mirror of
				https://github.com/invoiceninja/invoiceninja.git
				synced 2025-10-31 14:07:32 -04:00 
			
		
		
		
	Merge pull request #8173 from turbo124/v5-develop
Add company logo size to company settings object
This commit is contained in:
		
						commit
						2aea9eade3
					
				
							
								
								
									
										68
									
								
								.github/workflows/phpunit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										68
									
								
								.github/workflows/phpunit.yml
									
									
									
									
										vendored
									
									
								
							| @ -13,8 +13,12 @@ jobs: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         operating-system: ['ubuntu-20.04', 'ubuntu-22.04'] | ||||
|         php-versions: ['8.1'] | ||||
|         php-versions: ['8.1','8.2'] | ||||
|         phpunit-versions: ['latest'] | ||||
|         ci_node_total: [ 8 ] | ||||
|         ci_node_index: [ 0, 1, 2, 3, 4, 5, 6, 7] | ||||
|         laravel: [9.*] | ||||
|         dependency-version: [prefer-stable] | ||||
| 
 | ||||
|     env: | ||||
|       DB_DATABASE1: ninja | ||||
| @ -25,13 +29,14 @@ jobs: | ||||
|       DB_USERNAME: root | ||||
|       DB_PASSWORD: ninja | ||||
|       DB_HOST: '127.0.0.1' | ||||
|       REDIS_PORT: 6379 | ||||
|       BROADCAST_DRIVER: log | ||||
|       CACHE_DRIVER: file | ||||
|       QUEUE_CONNECTION: sync | ||||
|       SESSION_DRIVER: file | ||||
|       CACHE_DRIVER: redis | ||||
|       QUEUE_CONNECTION: redis | ||||
|       SESSION_DRIVER: redis | ||||
|       NINJA_ENVIRONMENT: hosted | ||||
|       MULTI_DB_ENABLED: false | ||||
|       NINJA_LICENSE: 123456 | ||||
|       NINJA_LICENSE: ${{ secrets.ninja_license }} | ||||
|       TRAVIS: true | ||||
|       MAIL_MAILER: log | ||||
| 
 | ||||
| @ -47,13 +52,18 @@ jobs: | ||||
|           MYSQL_DATABASE: ninja | ||||
|           MYSQL_ROOT_PASSWORD: ninja | ||||
|         options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 | ||||
|       redis: | ||||
|         image: redis | ||||
|         ports: | ||||
|           - 6379/tcp | ||||
|         options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Add hosts to /etc/hosts | ||||
|       run: | | ||||
|         sudo echo "127.0.0.1 ninja.test" | sudo tee -a /etc/hosts | ||||
| 
 | ||||
|     - name: Start mysql service | ||||
|     - name: Start MariaDB service | ||||
|       run: | | ||||
|         sudo systemctl start mysql.service | ||||
|     - name: Verify MariaDB connection | ||||
| @ -65,11 +75,11 @@ jobs: | ||||
|         while ! mysqladmin ping -h"127.0.0.1" -P"$DB_PORT" --silent; do | ||||
|           sleep 1 | ||||
|         done | ||||
|     - name: Setup PHP | ||||
|     - name: Setup PHP shivammathur/setup-php@v2 | ||||
|       uses: shivammathur/setup-php@v2 | ||||
|       with: | ||||
|         php-version: ${{ matrix.php-versions }} | ||||
|         extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml | ||||
|         extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml, redis | ||||
| 
 | ||||
|     - uses: actions/checkout@v1 | ||||
|       with: | ||||
| @ -79,32 +89,56 @@ jobs: | ||||
|     - name: Copy .env | ||||
|       run: | | ||||
|         cp .env.ci .env | ||||
| 
 | ||||
|     # - name: Get Composer Cache Directory | ||||
|     #   id: composer-cache | ||||
|     #   run: | | ||||
|     #     echo "::set-output name=dir::$(composer config cache-files-dir)" | ||||
|     # - uses: actions/cache@v2 | ||||
|     #   with: | ||||
|     #     path: ${{ steps.composer-cache.outputs.dir }} | ||||
|     #     key: ${{ runner.os }}-${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} | ||||
|     #     restore-keys: | | ||||
|     #       ${{ runner.os }}-${{ matrix.php }}-composer- | ||||
| 
 | ||||
|     - name: Cache dependencies actions/cache@v3 | ||||
|       uses: actions/cache@v3 | ||||
|       with: | ||||
|         path: ~/.composer/cache/files | ||||
|         key: dependencies-${{ matrix.dependency-version }}-laravel-${{ matrix.laravel }}-php-${{ matrix.php-versions }}-composer-${{ hashFiles('composer.json') }} | ||||
| 
 | ||||
|     - name: Install composer dependencies | ||||
|       run: | | ||||
|         composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} | ||||
|         composer install | ||||
| 
 | ||||
|     - name: Prepare Laravel Application | ||||
|       env: | ||||
|         REDIS_PORT: ${{ job.services.redis.ports['6379'] }} | ||||
|       run: | | ||||
|         php artisan key:generate | ||||
|         php artisan optimize | ||||
|         php artisan cache:clear | ||||
|         php artisan config:cache | ||||
|     - name: Create DB and schemas | ||||
|       run: | | ||||
|         mkdir -p database | ||||
|         touch database/database.sqlite | ||||
|         php artisan ninja:post-update | ||||
| 
 | ||||
|     - name: Migrate Database | ||||
|       run: | | ||||
|         php artisan migrate:fresh --seed --force && php artisan db:seed --force | ||||
|     - name: Prepare JS/CSS assets | ||||
|       run: | | ||||
|         npm i | ||||
|         npm run production | ||||
| 
 | ||||
|     # - name: Prepare JS/CSS assets | ||||
|     #   run: | | ||||
|     #     npm i | ||||
|     #     npm run production | ||||
|          | ||||
|     - name: Run Testsuite | ||||
|       run: | | ||||
|         cat .env | ||||
|         vendor/bin/snappdf download | ||||
|         vendor/bin/phpunit --testdox | ||||
|         tests/ci | ||||
|       env: | ||||
|         DB_PORT: ${{ job.services.mysql.ports[3306] }} | ||||
|         PHP_CS_FIXER_IGNORE_ENV: true | ||||
|         CI_NODE_TOTAL: ${{ matrix.ci_node_total }} | ||||
|         # Use the index from matrix as an environment variable | ||||
|         CI_NODE_INDEX: ${{ matrix.ci_node_index }} | ||||
| @ -1 +1 @@ | ||||
| 5.5.56 | ||||
| 5.5.57 | ||||
| @ -80,10 +80,7 @@ class CreateSingleAccount extends Command | ||||
|     public function handle() | ||||
|     { | ||||
| 
 | ||||
|         if(config('ninja.is_docker')) | ||||
|             return; | ||||
|          | ||||
|         if (!$this->confirm('Are you sure you want to inject dummy data?')) | ||||
|         if (Ninja::isHosted() || config('ninja.is_docker') || !$this->confirm('Are you sure you want to inject dummy data?')) | ||||
|             return; | ||||
| 
 | ||||
|         $this->invoice_repo = new InvoiceRepository(); | ||||
| @ -105,6 +102,11 @@ class CreateSingleAccount extends Command | ||||
|     { | ||||
|         $this->info('Creating Small Account and Company'); | ||||
| 
 | ||||
|         if($user = User::where('email','small@example.com')->first()) | ||||
|         { | ||||
|             $user->account->delete(); | ||||
|         } | ||||
| 
 | ||||
|         $account = Account::factory()->create(); | ||||
|         $company = Company::factory()->create([ | ||||
|             'account_id' => $account->id, | ||||
|  | ||||
| @ -13,11 +13,14 @@ namespace App\Console\Commands; | ||||
| 
 | ||||
| use App\Jobs\Util\VersionCheck; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\Traits\AppSetup; | ||||
| use Illuminate\Console\Command; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| 
 | ||||
| class PostUpdate extends Command | ||||
| { | ||||
|     use AppSetup; | ||||
| 
 | ||||
|     /** | ||||
|      * The name and signature of the console command. | ||||
|      * | ||||
| @ -83,6 +86,8 @@ class PostUpdate extends Command | ||||
| 
 | ||||
|         info('queue restarted'); | ||||
| 
 | ||||
|         $this->buildCache(true); | ||||
| 
 | ||||
|         VersionCheck::dispatch(); | ||||
| 
 | ||||
|         info('Sent for version check'); | ||||
|  | ||||
| @ -94,8 +94,6 @@ class Kernel extends ConsoleKernel | ||||
|         /* Performs system maintenance such as pruning the backup table */ | ||||
|         $schedule->job(new SystemMaintenance)->sundays()->at('02:30')->withoutOverlapping()->name('system-maintenance-job')->onOneServer(); | ||||
| 
 | ||||
|         /* Pulls in bank transactions from third party services */ | ||||
|         $schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer(); | ||||
| 
 | ||||
|         if (Ninja::isSelfHost()) { | ||||
| 
 | ||||
| @ -110,6 +108,9 @@ class Kernel extends ConsoleKernel | ||||
| 
 | ||||
|             $schedule->job(new AdjustEmailQuota)->dailyAt('23:30')->withoutOverlapping(); | ||||
| 
 | ||||
|             /* Pulls in bank transactions from third party services */ | ||||
|             $schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer(); | ||||
| 
 | ||||
|             //not used @deprecate
 | ||||
|             // $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
 | ||||
| 
 | ||||
|  | ||||
| @ -453,8 +453,10 @@ class CompanySettings extends BaseSettings | ||||
| 
 | ||||
|     public $show_email_footer = true; | ||||
| 
 | ||||
|     public $company_logo_size = '65%'; | ||||
| 
 | ||||
|     public static $casts = [ | ||||
|         'company_logo_size'                  => 'string', | ||||
|         'show_email_footer'                  => 'bool', | ||||
|         'email_alignment'                    => 'string', | ||||
|         'auto_bill_standard_invoices'        => 'bool', | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\DataMapper; | ||||
| namespace App\DataMapper\Schedule; | ||||
| 
 | ||||
| use App\Models\Client; | ||||
| use stdClass; | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\BankIntegration; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * BankIntegrationFilters. | ||||
|  | ||||
| @ -12,10 +12,7 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\BankTransaction; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * BankTransactionFilters. | ||||
| @ -77,87 +74,49 @@ class BankTransactionFilters extends QueryFilters | ||||
| 
 | ||||
|         $status_parameters = explode(',', $value); | ||||
| 
 | ||||
|         $status_array = []; | ||||
|          | ||||
|         $debit_or_withdrawal_array = []; | ||||
| 
 | ||||
|         if (in_array('all', $status_parameters)) { | ||||
|             return $this->builder; | ||||
|         } | ||||
| 
 | ||||
|         $this->builder->where(function ($query) use ($status_parameters){ | ||||
| 
 | ||||
|             $status_array = []; | ||||
|              | ||||
|             $debit_or_withdrawal_array = []; | ||||
| 
 | ||||
|             if (in_array('unmatched', $status_parameters)) { | ||||
|                 $status_array[] = BankTransaction::STATUS_UNMATCHED; | ||||
|             // $this->builder->orWhere('status_id', BankTransaction::STATUS_UNMATCHED);
 | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('matched', $status_parameters)) { | ||||
|                 $status_array[] = BankTransaction::STATUS_MATCHED; | ||||
|             // $this->builder->where('status_id', BankTransaction::STATUS_MATCHED);
 | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('converted', $status_parameters)) { | ||||
|                 $status_array[] = BankTransaction::STATUS_CONVERTED; | ||||
|             // $this->builder->where('status_id', BankTransaction::STATUS_CONVERTED);
 | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('deposits', $status_parameters)) { | ||||
|                 $debit_or_withdrawal_array[] = 'CREDIT'; | ||||
|             // $this->builder->where('base_type', 'CREDIT');
 | ||||
|             } | ||||
| 
 | ||||
|             if (in_array('withdrawals', $status_parameters)) { | ||||
|                 $debit_or_withdrawal_array[] = 'DEBIT'; | ||||
|             // $this->builder->where('base_type', 'DEBIT');
 | ||||
|             } | ||||
| 
 | ||||
|             if(count($status_array) >=1) { | ||||
|             $this->builder->whereIn('status_id', $status_array); | ||||
|                 $query->whereIn('status_id', $status_array); | ||||
|             } | ||||
| 
 | ||||
|             if(count($debit_or_withdrawal_array) >=1) { | ||||
|             $this->builder->orWhereIn('base_type', $debit_or_withdrawal_array); | ||||
|                 $query->orWhereIn('base_type', $debit_or_withdrawal_array); | ||||
|             } | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         return $this->builder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Filters the list based on the status | ||||
|      * archived, active, deleted. | ||||
|      * | ||||
|      * @param string filter | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function status(string $filter = '') : Builder | ||||
|     { | ||||
|         if (strlen($filter) == 0) { | ||||
|             return $this->builder; | ||||
|         } | ||||
| 
 | ||||
|         $filters = explode(',', $filter); | ||||
| 
 | ||||
|         return $this->builder->where(function ($query) use ($filters) { | ||||
| 
 | ||||
|             if (in_array(parent::STATUS_ACTIVE, $filters)) { | ||||
|                 $query->orWhereNull('deleted_at'); | ||||
|             } | ||||
| 
 | ||||
|             if (in_array(parent::STATUS_ARCHIVED, $filters)) { | ||||
|                 $query->orWhere(function ($query) use ($table) { | ||||
|                     $query->whereNotNull($table.'.deleted_at'); | ||||
| 
 | ||||
|                     if (! in_array($table, ['users'])) { | ||||
|                         $query->where($table.'.is_deleted', '=', 0); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             if (in_array(parent::STATUS_DELETED, $filters)) { | ||||
|                 $query->orWhere($table.'.is_deleted', '=', 1); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sorts the list based on $sort. | ||||
|      * | ||||
| @ -186,19 +145,6 @@ class BankTransactionFilters extends QueryFilters | ||||
|         return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the base query. | ||||
|      * | ||||
|      * @param int company_id | ||||
|      * @param User $user | ||||
|      * @return Builder | ||||
|      * @deprecated | ||||
|      */ | ||||
|     public function baseQuery(int $company_id, User $user) : Builder | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
| @ -206,7 +152,6 @@ class BankTransactionFilters extends QueryFilters | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     { | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -108,17 +108,17 @@ class ClientFilters extends QueryFilters | ||||
|         } | ||||
| 
 | ||||
|         return  $this->builder->where(function ($query) use ($filter) { | ||||
|             $query->where('clients.name', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('clients.id_number', 'like', '%'.$filter.'%') | ||||
|             $query->where('name', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('id_number', 'like', '%'.$filter.'%') | ||||
|                           ->orWhereHas('contacts', function ($query) use ($filter) { | ||||
|                               $query->where('first_name', 'like', '%'.$filter.'%'); | ||||
|                               $query->orWhere('last_name', 'like', '%'.$filter.'%'); | ||||
|                               $query->orWhere('email', 'like', '%'.$filter.'%'); | ||||
|                           }) | ||||
|                           ->orWhere('clients.custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('clients.custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('clients.custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('clients.custom_value4', 'like', '%'.$filter.'%'); | ||||
|                           ->orWhere('custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value4', 'like', '%'.$filter.'%'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -147,4 +147,14 @@ class ClientFilters extends QueryFilters | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
| 
 | ||||
|     public function filter_details(string $filter = '') | ||||
|     { | ||||
|          | ||||
|         if($filter == 'true') | ||||
|             return $this->builder->select('id', 'name', 'number', 'id_number'); | ||||
| 
 | ||||
|         return $this->builder; | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,9 +13,7 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Credit; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Carbon; | ||||
| 
 | ||||
| class CreditFilters extends QueryFilters | ||||
| { | ||||
| @ -44,20 +42,20 @@ class CreditFilters extends QueryFilters | ||||
|             return $this->builder; | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('draft', $status_parameters)) { | ||||
|             $this->builder->where('status_id', Credit::STATUS_DRAFT); | ||||
|         } | ||||
| 
 | ||||
|         if (in_array('partial', $status_parameters)) { | ||||
|             $this->builder->where('status_id', Credit::STATUS_PARTIAL); | ||||
|         } | ||||
|         $credit_filters = []; | ||||
| 
 | ||||
|         if (in_array('applied', $status_parameters)) { | ||||
|             $this->builder->where('status_id', Credit::STATUS_APPLIED); | ||||
|         } | ||||
|         if (in_array('draft', $status_parameters))  | ||||
|             $credit_filters[] = Credit::STATUS_DRAFT; | ||||
|          | ||||
|         //->where('due_date', '>', Carbon::now())
 | ||||
|         //->orWhere('partial_due_date', '>', Carbon::now());
 | ||||
|         if (in_array('partial', $status_parameters))  | ||||
|             $credit_filters[] = Credit::STATUS_PARTIAL; | ||||
| 
 | ||||
|         if (in_array('applied', $status_parameters))  | ||||
|             $credit_filters[] = Credit::STATUS_APPLIED; | ||||
| 
 | ||||
|         if(count($credit_filters) >=1) | ||||
|             $this->builder->whereIn('status_id', $credit_filters); | ||||
| 
 | ||||
|         return $this->builder; | ||||
|     } | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Design; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * DesignFilters. | ||||
| @ -27,6 +23,7 @@ class DesignFilters extends QueryFilters | ||||
|      * | ||||
|      * @param string query filter | ||||
|      * @return Builder | ||||
|      *  | ||||
|      * @deprecated | ||||
|      */ | ||||
|     public function filter(string $filter = ''): Builder | ||||
| @ -44,48 +41,17 @@ class DesignFilters extends QueryFilters | ||||
|      * Sorts the list based on $sort. | ||||
|      * | ||||
|      * @param string sort formatted as column|asc | ||||
|      *  | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function sort(string $sort): Builder | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if(is_array($sort_col)) | ||||
|             return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns the base query. | ||||
|      * | ||||
|      * @param int company_id | ||||
|      * @param User $user | ||||
|      * @return Builder | ||||
|      * @deprecated | ||||
|      */ | ||||
|     public function baseQuery(int $company_id, User $user) : Builder | ||||
|     { | ||||
|         $query = DB::table('designs') | ||||
|             ->join('companies', 'companies.id', '=', 'designs.company_id') | ||||
|             ->where('designs.company_id', '=', $company_id) | ||||
|             ->select( | ||||
|                 'designs.id', | ||||
|                 'designs.name', | ||||
|                 'designs.design', | ||||
|                 'designs.created_at', | ||||
|                 'designs.created_at as design_created_at', | ||||
|                 'designs.deleted_at', | ||||
|                 'designs.is_deleted', | ||||
|                 'designs.user_id', | ||||
|             ); | ||||
| 
 | ||||
|         /* | ||||
|          * If the user does not have permissions to view all invoices | ||||
|          * limit the user to only the invoices they have created | ||||
|          */ | ||||
|         if (Gate::denies('view-list', Design::class)) { | ||||
|             $query->where('designs.user_id', '=', $user->id); | ||||
|         } | ||||
| 
 | ||||
|         return $query; | ||||
|         return $this->builder; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -93,7 +59,7 @@ class DesignFilters extends QueryFilters | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         return $this->builder->where('company_id', auth()->user()->company()->id)->orWhere('company_id', null)->orderBy('id','asc'); | ||||
|  | ||||
| @ -12,7 +12,6 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Company; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -36,7 +35,14 @@ class DocumentFilters extends QueryFilters | ||||
|         return $this->builder; | ||||
|     } | ||||
| 
 | ||||
|     /* If client ID passed to this entity, simply return */ | ||||
|     /** | ||||
|      * Overriding method as client_id does | ||||
|      * not exist on this model, just pass | ||||
|      * back the builder | ||||
|      * @param  string $client_id The client hashed id. | ||||
|      *  | ||||
|      * @return Builder            | ||||
|      */ | ||||
|     public function client_id(string $client_id = ''): Builder | ||||
|     { | ||||
|         return $this->builder; | ||||
| @ -48,11 +54,14 @@ class DocumentFilters extends QueryFilters | ||||
|      * @param string sort formatted as column|asc | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function sort(string $sort) : Builder | ||||
|     public function sort(string $sort = '') : Builder | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if(is_array($sort_col)) | ||||
|             return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
| 
 | ||||
|         return $this->builder; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Expense; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * ExpenseCategoryFilters. | ||||
| @ -49,9 +45,9 @@ class ExpenseCategoryFilters extends QueryFilters | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['name'])) { | ||||
|         if (is_array($sort_col) && in_array($sort_col[1], ['asc', 'desc']) && in_array($sort_col[0], ['name'])) | ||||
|             return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
|         } | ||||
|          | ||||
| 
 | ||||
|         return $this->builder; | ||||
|     } | ||||
| @ -63,8 +59,6 @@ class ExpenseCategoryFilters extends QueryFilters | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     { | ||||
| 
 | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Expense; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * ExpenseFilters. | ||||
| @ -36,11 +32,11 @@ class ExpenseFilters extends QueryFilters | ||||
|         } | ||||
| 
 | ||||
|         return  $this->builder->where(function ($query) use ($filter) { | ||||
|             $query->where('expenses.public_notes', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('expenses.custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('expenses.custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('expenses.custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('expenses.custom_value4', 'like', '%'.$filter.'%'); | ||||
|             $query->where('public_notes', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value4', 'like', '%'.$filter.'%'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,6 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Invoice; | ||||
| use App\Models\User; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Carbon; | ||||
| @ -80,6 +79,9 @@ class InvoiceFilters extends QueryFilters | ||||
| 
 | ||||
|     public function number(string $number = '') :Builder | ||||
|     { | ||||
|         if(strlen($number) == 0) | ||||
|             return $this->builder; | ||||
|          | ||||
|         return $this->builder->where('number', $number); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -49,6 +48,7 @@ class PaymentFilters extends QueryFilters | ||||
|     { | ||||
| 
 | ||||
|         if($value == 'true'){ | ||||
| 
 | ||||
|             return $this->builder | ||||
|                         ->where('is_deleted',0) | ||||
|                         ->where(function ($query){ | ||||
| @ -72,7 +72,10 @@ class PaymentFilters extends QueryFilters | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if(is_array($sort_col)) | ||||
|             return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public function number(string $number) : Builder | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Design; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * PaymentTermFilters. | ||||
| @ -56,12 +52,10 @@ class PaymentTermFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         // return $this->builder->whereCompanyId(auth()->user()->company()->id)->orWhere('company_id', null);
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Project; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * ProjectFilters. | ||||
| @ -52,6 +48,7 @@ class ProjectFilters extends QueryFilters | ||||
|     { | ||||
|         $sort_col = explode('|', $sort); | ||||
| 
 | ||||
|         if(is_array($sort_col)) | ||||
|             return $this->builder->orderBy($sort_col[0], $sort_col[1]); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,6 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\PurchaseOrder; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| class PurchaseOrderFilters extends QueryFilters | ||||
| @ -70,7 +69,7 @@ class PurchaseOrderFilters extends QueryFilters | ||||
|             if(count($status_parameters) >=1) { | ||||
|                 $query->whereIn('status_id', $status_parameters); | ||||
|             } | ||||
|         }) | ||||
|         }); | ||||
| 
 | ||||
|         return $this->builder; | ||||
|     } | ||||
|  | ||||
| @ -12,7 +12,6 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\Quote; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\RecurringExpense; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * RecurringExpenseFilters. | ||||
| @ -36,12 +32,11 @@ class RecurringExpenseFilters extends QueryFilters | ||||
|         } | ||||
| 
 | ||||
|         return $this->builder->where(function ($query) use ($filter) { | ||||
|             $query->where('recurring_expenses.name', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('recurring_expenses.id_number', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('recurring_expenses.custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('recurring_expenses.custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('recurring_expenses.custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('recurring_expenses.custom_value4', 'like', '%'.$filter.'%'); | ||||
|             $query->where('public_notes', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value1', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value2', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value3', 'like', '%'.$filter.'%') | ||||
|                           ->orWhere('custom_value4', 'like', '%'.$filter.'%'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -12,7 +12,6 @@ | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\RecurringInvoice; | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -77,8 +76,11 @@ class RecurringInvoiceFilters extends QueryFilters | ||||
|         if (in_array('completed', $status_parameters))  | ||||
|             $recurring_filters[] = RecurringInvoice::STATUS_COMPLETED; | ||||
| 
 | ||||
|         if(count($recurring_filters) >= 1) | ||||
|             return $this->builder->whereIn('status_id', $recurring_filters); | ||||
| 
 | ||||
|         return $this->builder; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -56,9 +55,9 @@ class RecurringQuoteFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use App\Models\Webhook; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * SubscriptionFilters. | ||||
| @ -56,9 +52,9 @@ class SubscriptionFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -66,9 +65,9 @@ class SystemLogFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| @ -100,9 +99,9 @@ class TaskFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -53,9 +52,9 @@ class TaskStatusFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -53,9 +52,9 @@ class TaxRateFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,10 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * TokenFilters. | ||||
| @ -55,9 +52,9 @@ class TokenFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,6 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| 
 | ||||
| /** | ||||
| @ -61,10 +60,29 @@ class UserFilters extends QueryFilters | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     { | ||||
|         //return $this->builder->user_companies()->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         return $this->builder->whereHas('company_users', function ($q) { | ||||
|             $q->where('company_id', '=', auth()->user()->company()->id); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Overrides the base with() function as no company ID  | ||||
|      * exists on the user table | ||||
|      *  | ||||
|      * @param  string $value Hashed ID of the user to return back in the dataset | ||||
|      *  | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function with(string $value = ''): Builder | ||||
|     { | ||||
| 
 | ||||
|         if(strlen($value) == 0) | ||||
|             return $this->builder; | ||||
| 
 | ||||
|         return $this->builder | ||||
|             ->orWhere($this->with_property, $value) | ||||
|             ->orderByRaw("{$this->with_property} = ? DESC", [$value]) | ||||
|             ->where('account_id', auth()->user()->account_id); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use App\Models\Vendor; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * VendorFilters. | ||||
| @ -66,12 +62,10 @@ class VendorFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
| 
 | ||||
|         //return $this->builder->whereCompanyId(auth()->user()->company()->id);
 | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -11,11 +11,7 @@ | ||||
| 
 | ||||
| namespace App\Filters; | ||||
| 
 | ||||
| use App\Models\User; | ||||
| use App\Models\Webhook; | ||||
| use Illuminate\Database\Eloquent\Builder; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Gate; | ||||
| 
 | ||||
| /** | ||||
|  * TokenFilters. | ||||
| @ -36,7 +32,7 @@ class WebhookFilters extends QueryFilters | ||||
|         } | ||||
| 
 | ||||
|         return  $this->builder->where(function ($query) use ($filter) { | ||||
|             $query->where('webhooks.target_url', 'like', '%'.$filter.'%'); | ||||
|             $query->where('target_url', 'like', '%'.$filter.'%'); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| @ -56,9 +52,9 @@ class WebhookFilters extends QueryFilters | ||||
|     /** | ||||
|      * Filters the query by the users company ID. | ||||
|      * | ||||
|      * @return Illuminate\Database\Query\Builder | ||||
|      * @return Builder | ||||
|      */ | ||||
|     public function entityFilter() | ||||
|     public function entityFilter(): Builder | ||||
|     { | ||||
|         return $this->builder->company(); | ||||
|     } | ||||
|  | ||||
| @ -24,6 +24,9 @@ use Illuminate\View\View; | ||||
| function isActive($page, bool $boolean = false) | ||||
| { | ||||
|     $current_page = Route::currentRouteName(); | ||||
|     $action = Route::currentRouteAction(); // string
 | ||||
| 
 | ||||
|     $show = str_replace(['.show','payment_methodss','documentss','subscriptionss','paymentss'],['s.index','payment_methods','documents','subscriptions','payments'], $current_page); | ||||
| 
 | ||||
|     if ($page == $current_page && $boolean) { | ||||
|         return true; | ||||
| @ -33,6 +36,12 @@ function isActive($page, bool $boolean = false) | ||||
|         return 'bg-gray-200'; | ||||
|     } | ||||
| 
 | ||||
|     if(($page == $show) && $boolean){ | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     return false; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -56,7 +56,7 @@ class LoginController extends BaseController | ||||
|      *     description="Authentication", | ||||
|      *     @OA\ExternalDocumentation( | ||||
|      *         description="Find out more", | ||||
|      *         url="http://docs.invoiceninja.com" | ||||
|      *         url="https://invoiceninja.github.io" | ||||
|      *     ) | ||||
|      * ) | ||||
|      */ | ||||
|  | ||||
| @ -335,7 +335,7 @@ class BankTransactionRuleController extends BaseController | ||||
|      * | ||||
|      * @OA\Post( | ||||
|      *      path="/api/v1/bank_transaction_rules", | ||||
|      *      operationId="storeBankTransaction", | ||||
|      *      operationId="storeBankTransactionRule", | ||||
|      *      tags={"bank_transaction_rules"}, | ||||
|      *      summary="Adds a bank_transaction rule", | ||||
|      *      description="Adds an bank_transaction to a company", | ||||
|  | ||||
| @ -160,7 +160,7 @@ class PaymentController extends Controller | ||||
|         } | ||||
| 
 | ||||
|         if (property_exists($payment_hash->data, 'billing_context')) { | ||||
|             $billing_subscription = \App\Models\Subscription::find($payment_hash->data->billing_context->subscription_id); | ||||
|             $billing_subscription = \App\Models\Subscription::find($this->decodePrimaryKey($payment_hash->data->billing_context->subscription_id)); | ||||
| 
 | ||||
|             return (new SubscriptionService($billing_subscription))->completePurchase($payment_hash); | ||||
|         } | ||||
|  | ||||
| @ -19,8 +19,8 @@ | ||||
|  *         url="https://ninja.test", | ||||
|  *     ), | ||||
|  *     @OA\ExternalDocumentation( | ||||
|  *         description="http://docs.invoiceninja.com", | ||||
|  *         url="http://docs.invoiceninja.com" | ||||
|  *         description="https://invoiceninja.github.io", | ||||
|  *         url="https://invoiceninja.github.io" | ||||
|  *     ), | ||||
|  * ), | ||||
|  */ | ||||
|  | ||||
| @ -109,6 +109,8 @@ class SelfUpdateController extends BaseController | ||||
| 
 | ||||
|         $zipFile->deleteFromName(".htaccess"); | ||||
|          | ||||
|         $zipFile->rewrite(); | ||||
| 
 | ||||
|         $zipFile->extractTo(base_path()); | ||||
| 
 | ||||
|         $zipFile->close(); | ||||
|  | ||||
| @ -323,7 +323,7 @@ class TaskSchedulerController extends BaseController | ||||
|      *          @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), | ||||
|      *          @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), | ||||
|      *          @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), | ||||
|      *          @OA\JsonContent(ref="#/components/schemas/TaskScheduleSchema"), | ||||
|      *          @OA\JsonContent(ref="#/components/schemas/TaskSchedulerSchema"), | ||||
|      *       ), | ||||
|      *       @OA\Response( | ||||
|      *          response=422, | ||||
|  | ||||
| @ -403,10 +403,10 @@ class BillingPortalPurchase extends Component | ||||
|             ->save(); | ||||
| 
 | ||||
|         Cache::put($this->hash, [ | ||||
|             'subscription_id' => $this->subscription->id, | ||||
|             'subscription_id' => $this->subscription->hashed_id, | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'invoice_id' => $this->invoice->id, | ||||
|             'client_id' => $this->contact->client->hashed_id, | ||||
|             'invoice_id' => $this->invoice->hashed_id, | ||||
|             'context' => 'purchase', | ||||
|             'campaign' => $this->campaign, | ||||
|         ], now()->addMinutes(60)); | ||||
|  | ||||
| @ -483,7 +483,12 @@ class BillingPortalPurchasev2 extends Component | ||||
|      */ | ||||
|     protected function getPaymentMethods() :self | ||||
|     { | ||||
|         if($this->contact) | ||||
|         nlog("total amount = {$this->float_amount_total}"); | ||||
| 
 | ||||
|         if($this->float_amount_total == 0) | ||||
|             $this->methods = []; | ||||
| 
 | ||||
|         if($this->contact && $this->float_amount_total >= 1) | ||||
|             $this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total); | ||||
| 
 | ||||
|         return $this; | ||||
| @ -526,7 +531,7 @@ class BillingPortalPurchasev2 extends Component | ||||
|         } | ||||
| 
 | ||||
|         $data = [ | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'client_id' => $this->contact->client->hashed_id, | ||||
|             'date' => now()->format('Y-m-d'), | ||||
|             'invitations' => [[ | ||||
|                 'key' => '', | ||||
| @ -547,10 +552,10 @@ class BillingPortalPurchasev2 extends Component | ||||
|             ->save(); | ||||
| 
 | ||||
|         Cache::put($this->hash, [ | ||||
|             'subscription_id' => $this->subscription->id, | ||||
|             'subscription_id' => $this->subscription->hashed_id, | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'invoice_id' => $this->invoice->id, | ||||
|             'client_id' => $this->contact->client->hashed_id, | ||||
|             'invoice_id' => $this->invoice->hashed_id, | ||||
|             'context' => 'purchase', | ||||
|             'campaign' => $this->campaign, | ||||
|             'bundle' => $this->bundle, | ||||
| @ -562,17 +567,62 @@ class BillingPortalPurchasev2 extends Component | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Starts the trial | ||||
|      *  | ||||
|      * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse | ||||
|      */ | ||||
|     public function handleTrial() | ||||
|     { | ||||
|         return $this->subscription->service()->startTrial([ | ||||
|             'email' => $this->email ?? $this->contact->email, | ||||
|             'quantity' => $this->quantity, | ||||
|             'contact_id' => $this->contact->id, | ||||
|             'client_id' => $this->contact->client->id, | ||||
|             'contact_id' => $this->contact->hashed_id, | ||||
|             'client_id' => $this->contact->client->hashed_id, | ||||
|             'bundle' => $this->bundle, | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * When the subscription total comes to $0 we  | ||||
|      * pass back a $0 Invoice. | ||||
|      * | ||||
|      * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse | ||||
|      */ | ||||
|     public function handlePaymentNotRequired() | ||||
|     { | ||||
| 
 | ||||
|         $eligibility_check = $this->subscription->service()->isEligible($this->contact); | ||||
| 
 | ||||
|         if(is_array($eligibility_check) && $eligibility_check['message'] != 'Success'){ | ||||
|              | ||||
|             $this->is_eligible = false; | ||||
|             $this->not_eligible_message = $eligibility_check['message']; | ||||
|             return $this; | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         $invoice = $this->subscription | ||||
|             ->service() | ||||
|             ->createInvoiceV2($this->bundle, $this->contact->client_id, $this->valid_coupon) | ||||
|             ->service() | ||||
|             ->fillDefaults() | ||||
|             ->adjustInventory() | ||||
|             ->save(); | ||||
| 
 | ||||
|             $invoice->number = null; | ||||
| 
 | ||||
|             $invoice->service() | ||||
|                     ->markPaid() | ||||
|                     ->save(); | ||||
| 
 | ||||
|             return $this->subscription | ||||
|                         ->service() | ||||
|                         ->handleNoPaymentFlow($invoice, $this->bundle, $this->contact); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| @ -607,43 +657,6 @@ class BillingPortalPurchasev2 extends Component | ||||
|      | ||||
|     } | ||||
| 
 | ||||
|     // /**
 | ||||
|     //  * Handle user authentication
 | ||||
|     //  *
 | ||||
|     //  * @return $this|bool|void
 | ||||
|     //  */
 | ||||
|     // public function authenticate()
 | ||||
|     // {
 | ||||
|     //     $this->validate();
 | ||||
| 
 | ||||
|     //     $contact = ClientContact::where('email', $this->email)
 | ||||
|     //         ->where('company_id', $this->subscription->company_id)
 | ||||
|     //         ->first();
 | ||||
| 
 | ||||
|     //     if ($contact && $this->steps['existing_user'] === false) {
 | ||||
|     //         return $this->steps['existing_user'] = true;
 | ||||
|     //     }
 | ||||
| 
 | ||||
|     //     if ($contact && $this->steps['existing_user']) {
 | ||||
|     //         $attempt = Auth::guard('contact')->attempt(['email' => $this->email, 'password' => $this->password, 'company_id' => $this->subscription->company_id]);
 | ||||
| 
 | ||||
|     //         return $attempt
 | ||||
|     //             ? $this->getPaymentMethods($contact)
 | ||||
|     //             : session()->flash('message', 'These credentials do not match our records.');
 | ||||
|     //     }
 | ||||
| 
 | ||||
|     //     $this->steps['existing_user'] = false;
 | ||||
| 
 | ||||
|     //     $contact = $this->createBlankClient();
 | ||||
| 
 | ||||
|     //     if ($contact && $contact instanceof ClientContact) {
 | ||||
|     //         $this->getPaymentMethods($contact);
 | ||||
|     //     }
 | ||||
|     // }
 | ||||
| 
 | ||||
|     | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Create a blank client. Used for new customers purchasing. | ||||
|      * | ||||
|  | ||||
| @ -42,7 +42,7 @@ class PaymentsTable extends Component | ||||
|     public function render() | ||||
|     { | ||||
|         $query = Payment::query() | ||||
|             ->with('type', 'client') | ||||
|             ->with('type', 'client', 'invoices') | ||||
|             ->where('company_id', $this->company->id) | ||||
|             ->where('client_id', auth()->guard('contact')->user()->client_id) | ||||
|             ->whereIn('status_id', [Payment::STATUS_FAILED, Payment::STATUS_COMPLETED, Payment::STATUS_PENDING, Payment::STATUS_REFUNDED, Payment::STATUS_PARTIALLY_REFUNDED]) | ||||
|  | ||||
| @ -106,11 +106,11 @@ class SubscriptionPlanSwitch extends Component | ||||
|             ]); | ||||
| 
 | ||||
|             Cache::put($this->hash, [ | ||||
|                 'subscription_id' => $this->target->id, | ||||
|                 'target_id' => $this->target->id, | ||||
|                 'recurring_invoice' => $this->recurring_invoice->id, | ||||
|                 'client_id' => $this->recurring_invoice->client->id, | ||||
|                 'invoice_id' => $this->state['invoice']->id, | ||||
|                 'subscription_id' => $this->target->hashed_id, | ||||
|                 'target_id' => $this->target->hashed_id, | ||||
|                 'recurring_invoice' => $this->recurring_invoice->hashed_id, | ||||
|                 'client_id' => $this->recurring_invoice->client->hashed_id, | ||||
|                 'invoice_id' => $this->state['invoice']->hashed_id, | ||||
|                 'context' => 'change_plan', | ||||
|                 now()->addMinutes(60), ] | ||||
|                 ); | ||||
|  | ||||
| @ -78,8 +78,6 @@ class StoreShopClientRequest extends Request | ||||
| 
 | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         //@todo implement feature permissions for > 100 clients
 | ||||
|         //
 | ||||
|         $settings = ClientSettings::defaults(); | ||||
| 
 | ||||
|         if (array_key_exists('settings', $input) && ! empty($input['settings'])) { | ||||
|  | ||||
| @ -9,7 +9,7 @@ | ||||
|  * @license https://www.elastic.co/licensing/elastic-license | ||||
|  */ | ||||
| 
 | ||||
| namespace App\Http\Requests\Task; | ||||
| namespace App\Http\Requests\TaskScheduler; | ||||
| 
 | ||||
| use App\Http\Requests\Request; | ||||
| 
 | ||||
|  | ||||
| @ -63,8 +63,6 @@ class StoreUserRequest extends Request | ||||
|     { | ||||
|         $input = $this->all(); | ||||
| 
 | ||||
|         //unique user rule - check company_user table for user_id / company_id  / account_id if none exist we can add the user. ELSE return false
 | ||||
| 
 | ||||
|         if (array_key_exists('email', $input)) { | ||||
|             $input['email'] = trim($input['email']); | ||||
|         } | ||||
| @ -79,12 +77,10 @@ class StoreUserRequest extends Request | ||||
|             } | ||||
| 
 | ||||
|             if (! isset($input['company_user']['settings'])) { | ||||
|                 //$input['company_user']['settings'] = DefaultSettings::userSettings();
 | ||||
|                 $input['company_user']['settings'] = null; | ||||
|             } | ||||
|         } else { | ||||
|             $input['company_user'] = [ | ||||
|                 //'settings' => DefaultSettings::userSettings(),
 | ||||
|                 'settings' => null, | ||||
|                 'permissions' => '', | ||||
|             ]; | ||||
|  | ||||
| @ -319,7 +319,7 @@ class MatchBankTransactions implements ShouldQueue | ||||
| 
 | ||||
|                 }); | ||||
| 
 | ||||
|         }, 1); | ||||
|         }, 2); | ||||
| 
 | ||||
|         if(!$this->invoice) | ||||
|             return; | ||||
|  | ||||
| @ -45,6 +45,7 @@ class SubscriptionCron | ||||
|             $invoices = Invoice::where('is_deleted', 0) | ||||
|                               ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||
|                               ->where('balance', '>', 0) | ||||
|                               ->where('is_proforma',0) | ||||
|                               ->whereDate('due_date', '<=', now()->addDay()->startOfDay()) | ||||
|                               ->whereNull('deleted_at') | ||||
|                               ->whereNotNull('subscription_id') | ||||
| @ -74,6 +75,7 @@ class SubscriptionCron | ||||
|                 $invoices = Invoice::where('is_deleted', 0) | ||||
|                                   ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) | ||||
|                                   ->where('balance', '>', 0) | ||||
|                                   ->where('is_proforma',0) | ||||
|                                   ->whereDate('due_date', '<=', now()->addDay()->startOfDay()) | ||||
|                                   ->whereNull('deleted_at') | ||||
|                                   ->whereNotNull('subscription_id') | ||||
|  | ||||
| @ -56,12 +56,5 @@ class InvoiceWorkflowSettings implements ShouldQueue | ||||
|             /* Throws: Payment amount xxx does not match invoice totals. */ | ||||
|             $this->base_repository->archive($this->invoice); | ||||
|         } | ||||
| 
 | ||||
|         //@TODO this setting should only fire for recurring invoices
 | ||||
|         // if ($this->client->getSetting('auto_email_invoice')) {
 | ||||
|         //    $this->invoice->invitations->each(function ($invitation, $key) {
 | ||||
|         //         $this->invoice->service()->sendEmail($invitation->contact);
 | ||||
|         //    });
 | ||||
|         // }
 | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -44,6 +44,7 @@ class BankTransactionSync implements ShouldQueue | ||||
|      */ | ||||
|     public function handle() | ||||
|     { | ||||
| 
 | ||||
|         //multiDB environment, need to
 | ||||
|         foreach (MultiDB::$dbs as $db)  | ||||
|         { | ||||
|  | ||||
| @ -29,6 +29,8 @@ class UpdateOrCreateProduct implements ShouldQueue | ||||
| 
 | ||||
|     public $company; | ||||
| 
 | ||||
|     public $deleteWhenMissingModels = true; | ||||
| 
 | ||||
|     /** | ||||
|      * Create a new job instance. | ||||
|      * | ||||
|  | ||||
| @ -212,6 +212,11 @@ class Payment extends BaseModel | ||||
|         return Number::formatMoney($this->amount, $this->client); | ||||
|     } | ||||
| 
 | ||||
|     public function formatAmount(float $amount): string | ||||
|     { | ||||
|         return Number::formatMoney($amount, $this->client); | ||||
|     } | ||||
| 
 | ||||
|     public function clientPaymentDate() | ||||
|     { | ||||
|         if (! $this->date) { | ||||
|  | ||||
| @ -43,4 +43,5 @@ class Paymentable extends Pivot | ||||
|     { | ||||
|         return $this->belongsTo(Payment::class); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -27,7 +27,6 @@ class ClientPresenter extends EntityPresenter | ||||
|             return $this->entity->name; | ||||
|         } | ||||
| 
 | ||||
|         //$contact = $this->entity->primary_contact->first();
 | ||||
|         $contact = $this->entity->contacts->whereNotNull('email')->first(); | ||||
| 
 | ||||
|         $contact_name = 'No Contact Set'; | ||||
|  | ||||
| @ -370,11 +370,6 @@ class User extends Authenticatable implements MustVerifyEmail | ||||
|                 (is_int(stripos($this->token()->cu->permissions, $all_permission))) || | ||||
|                 (is_int(stripos($this->token()->cu->permissions, $permission))); | ||||
| 
 | ||||
|         //23-03-2021 - stripos return an int if true and bool false, but 0 is also interpreted as false, so we simply use is_int() to verify state
 | ||||
|         // return  $this->isOwner() ||
 | ||||
|         //         $this->isAdmin() ||
 | ||||
|         //         (stripos($this->company_user->permissions, $all_permission) !== false) ||
 | ||||
|         //         (stripos($this->company_user->permissions, $permission) !== false);
 | ||||
|     } | ||||
| 
 | ||||
|     public function documents() | ||||
|  | ||||
| @ -289,7 +289,7 @@ class BaseDriver extends AbstractPaymentDriver | ||||
|         event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); | ||||
| 
 | ||||
|         if (property_exists($this->payment_hash->data, 'billing_context') && $status == Payment::STATUS_COMPLETED) { | ||||
|             $billing_subscription = \App\Models\Subscription::find($this->payment_hash->data->billing_context->subscription_id); | ||||
|             $billing_subscription = \App\Models\Subscription::find($this->decodePrimaryKey($this->payment_hash->data->billing_context->subscription_id)); | ||||
| 
 | ||||
|             // To access campaign hash => $this->payment_hash->data->billing_context->campaign;
 | ||||
|             // To access campaign data => Cache::get(CAMPAIGN_HASH)
 | ||||
|  | ||||
| @ -18,18 +18,16 @@ use App\Models\Invoice; | ||||
| use App\Models\Proposal; | ||||
| use App\Utils\Ninja; | ||||
| use App\Utils\TruthSource; | ||||
| use Illuminate\Cache\RateLimiting\Limit; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Database\Eloquent\Relations\Relation; | ||||
| use Illuminate\Mail\Mailer; | ||||
| use Illuminate\Queue\Events\JobProcessing; | ||||
| use Illuminate\Support\Facades\App; | ||||
| use Illuminate\Support\Facades\Artisan; | ||||
| use Illuminate\Support\Facades\Blade; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Mail; | ||||
| use Illuminate\Support\Facades\ParallelTesting; | ||||
| use Illuminate\Support\Facades\Queue; | ||||
| use Illuminate\Support\Facades\RateLimiter; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| use Illuminate\Support\ServiceProvider; | ||||
| use Livewire\Livewire; | ||||
| @ -111,13 +109,4 @@ class AppServiceProvider extends ServiceProvider | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Register any application services. | ||||
|      * | ||||
|      * @return void | ||||
|      */ | ||||
|     public function register() | ||||
|     { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -52,7 +52,6 @@ class ClientRepository extends BaseRepository | ||||
|      * @return     Client|Client|null  Client Object | ||||
|      * | ||||
|      * @throws \Laracasts\Presenter\Exceptions\PresenterException | ||||
|      * @todo       Write tests to make sure that custom client numbers work as expected. | ||||
|      */ | ||||
|     public function save(array $data, Client $client) : ?Client | ||||
|     { | ||||
|  | ||||
| @ -42,7 +42,7 @@ class ClientService | ||||
|                 $this->client->balance += $amount; | ||||
|                 $this->client->save(); | ||||
| 
 | ||||
|             }, 1); | ||||
|             }, 2); | ||||
|         } | ||||
|         catch (\Throwable $throwable) { | ||||
|             nlog("DB ERROR " . $throwable->getMessage()); | ||||
| @ -63,7 +63,7 @@ class ClientService | ||||
|                 $this->client->paid_to_date += $paid_to_date; | ||||
|                 $this->client->save(); | ||||
| 
 | ||||
|             }, 1); | ||||
|             }, 2); | ||||
|         } | ||||
|         catch (\Throwable $throwable) { | ||||
|             nlog("DB ERROR " . $throwable->getMessage()); | ||||
| @ -82,7 +82,7 @@ class ClientService | ||||
|             $this->client->paid_to_date += $amount; | ||||
|             $this->client->save(); | ||||
| 
 | ||||
|         }, 1); | ||||
|         }, 2); | ||||
| 
 | ||||
|         return $this; | ||||
| 
 | ||||
|  | ||||
| @ -230,6 +230,14 @@ class InstantPayment | ||||
|         elseif($this->request->hash){ | ||||
|             $hash_data['billing_context'] = Cache::get($this->request->hash); | ||||
|         } | ||||
|         elseif($old_hash = PaymentHash::where('fee_invoice_id', $first_invoice->id)->whereNull('payment_id')->first()) { | ||||
| 
 | ||||
|             if(isset($old_hash->data->billing_context)) | ||||
|             { | ||||
|                 $hash_data['billing_context'] = $old_hash->data->billing_context; | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         $payment_hash = new PaymentHash; | ||||
|         $payment_hash->hash = Str::random(32); | ||||
|  | ||||
| @ -51,7 +51,7 @@ class DeletePayment | ||||
|                      | ||||
|             } | ||||
| 
 | ||||
|         }, 1); | ||||
|         }, 2); | ||||
| 
 | ||||
|         return $this->payment; | ||||
|      | ||||
|  | ||||
| @ -37,7 +37,7 @@ class SendEmail | ||||
|         $contact = $this->payment->client->contacts()->first(); | ||||
| 
 | ||||
|         if ($contact?->email) | ||||
|             EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(3)); | ||||
|             EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(8)); | ||||
|           | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -47,7 +47,7 @@ class SchedulerService | ||||
| 
 | ||||
|         //Email only the selected clients
 | ||||
|         if(count($this->scheduler->parameters['clients']) >= 1) | ||||
|             $query->where('id', $this->transformKeys($this->scheduler->parameters['clients'])); | ||||
|             $query->whereIn('id', $this->transformKeys($this->scheduler->parameters['clients'])); | ||||
|       | ||||
|         $query->cursor() | ||||
|             ->each(function ($_client){ | ||||
| @ -119,40 +119,40 @@ class SchedulerService | ||||
| 
 | ||||
|         switch ($this->scheduler->frequency_id) { | ||||
|             case RecurringInvoice::FREQUENCY_DAILY: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addDay(); | ||||
|                 $next_run = now()->startOfDay()->addDay(); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_WEEKLY: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addWeek(); | ||||
|                 $next_run = now()->startOfDay()->addWeek(); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_TWO_WEEKS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addWeeks(2); | ||||
|                 $next_run = now()->startOfDay()->addWeeks(2); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_FOUR_WEEKS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addWeeks(4); | ||||
|                 $next_run = now()->startOfDay()->addWeeks(4); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_MONTHLY: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addMonthNoOverflow(); | ||||
|                 $next_run = now()->startOfDay()->addMonthNoOverflow(); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_TWO_MONTHS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addMonthsNoOverflow(2); | ||||
|                 $next_run = now()->startOfDay()->addMonthsNoOverflow(2); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_THREE_MONTHS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addMonthsNoOverflow(3); | ||||
|                 $next_run = now()->startOfDay()->addMonthsNoOverflow(3); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_FOUR_MONTHS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addMonthsNoOverflow(4); | ||||
|                 $next_run = now()->startOfDay()->addMonthsNoOverflow(4); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_SIX_MONTHS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addMonthsNoOverflow(6); | ||||
|                 $next_run = now()->startOfDay()->addMonthsNoOverflow(6); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_ANNUALLY: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addYear(); | ||||
|                 $next_run = now()->startOfDay()->addYear(); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_TWO_YEARS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addYears(2); | ||||
|                 $next_run = now()->startOfDay()->addYears(2); | ||||
|                 break; | ||||
|             case RecurringInvoice::FREQUENCY_THREE_YEARS: | ||||
|                 $next_run = Carbon::parse($this->scheduler->next_run)->startOfDay()->addYears(3); | ||||
|                 $next_run = now()->startOfDay()->addYears(3); | ||||
|                 break; | ||||
|             default: | ||||
|                 $next_run =  null; | ||||
|  | ||||
| @ -167,7 +167,7 @@ class SubscriptionService | ||||
|     public function startTrial(array $data) | ||||
|     { | ||||
|         // Redirects from here work just fine. Livewire will respect it.
 | ||||
|         $client_contact = ClientContact::find($data['contact_id']); | ||||
|         $client_contact = ClientContact::find($this->decodePrimaryKey($data['contact_id'])); | ||||
| 
 | ||||
|         if(!$this->subscription->trial_enabled) | ||||
|             return new \Exception("Trials are disabled for this product"); | ||||
| @ -331,6 +331,7 @@ class SubscriptionService | ||||
|      * We refund unused days left. | ||||
|      * | ||||
|      * @param  Invoice $invoice | ||||
|      *  | ||||
|      * @return float | ||||
|      */ | ||||
|     private function calculateProRataRefundForSubscription($invoice) :float | ||||
| @ -338,6 +339,20 @@ class SubscriptionService | ||||
|         if(!$invoice || !$invoice->date || $invoice->status_id != Invoice::STATUS_PAID) | ||||
|             return 0; | ||||
| 
 | ||||
|         /*Remove previous refunds from the calculation of the amount*/ | ||||
|         $invoice->line_items = collect($invoice->line_items)->filter(function($item){ | ||||
| 
 | ||||
|             if($item->product_key == ctrans("texts.refund")) | ||||
|             { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             return true; | ||||
| 
 | ||||
|         })->toArray(); | ||||
| 
 | ||||
|         $amount = $invoice->calc()->getTotal(); | ||||
| 
 | ||||
|         $start_date = Carbon::parse($invoice->date); | ||||
| 
 | ||||
|         $current_date = now(); | ||||
| @ -346,7 +361,7 @@ class SubscriptionService | ||||
| 
 | ||||
|         $days_in_frequency = $this->getDaysInFrequency(); | ||||
| 
 | ||||
|         $pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $invoice->amount ,2); | ||||
|         $pro_rata_refund = round((($days_in_frequency - $days_of_subscription_used)/$days_in_frequency) * $amount ,2); | ||||
| 
 | ||||
|         return max(0, $pro_rata_refund); | ||||
| 
 | ||||
| @ -670,6 +685,8 @@ class SubscriptionService | ||||
|             nlog("pro rata refund = {$pro_rata_refund_amount}"); | ||||
|         } | ||||
| 
 | ||||
|         nlog("{$pro_rata_refund_amount} + {$pro_rata_charge_amount} + {$this->subscription->price}"); | ||||
|          | ||||
|         $total_payable = $pro_rata_refund_amount + $pro_rata_charge_amount + $this->subscription->price; | ||||
| 
 | ||||
|         if($total_payable > 0) | ||||
| @ -734,7 +751,7 @@ class SubscriptionService | ||||
|     { | ||||
|         nlog("handle plan change"); | ||||
| 
 | ||||
|         $old_recurring_invoice = RecurringInvoice::find($payment_hash->data->billing_context->recurring_invoice); | ||||
|         $old_recurring_invoice = RecurringInvoice::find($this->decodePrimaryKey($payment_hash->data->billing_context->recurring_invoice)); | ||||
| 
 | ||||
|         if(!$old_recurring_invoice)         | ||||
|             return $this->handleRedirect('/client/recurring_invoices/'); | ||||
| @ -1291,7 +1308,12 @@ class SubscriptionService | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private function getDaysInFrequency() | ||||
|     /** | ||||
|      * Get the number of days in the currency frequency | ||||
|      *  | ||||
|      * @return int Number of days | ||||
|      */ | ||||
|     private function getDaysInFrequency() :int | ||||
|     { | ||||
| 
 | ||||
|         switch ($this->subscription->frequency_id) { | ||||
| @ -1325,7 +1347,15 @@ class SubscriptionService | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function getNextDateForFrequency($date, $frequency) | ||||
|     /** | ||||
|      * Get the next date by frequency_id | ||||
|      *  | ||||
|      * @param  Carbon $date      The current carbon date | ||||
|      * @param  int               $frequency The frequncy_id of the subscription | ||||
|      *  | ||||
|      * @return ?Carbon           The next date carbon object | ||||
|      */ | ||||
|     public function getNextDateForFrequency($date, $frequency) :?Carbon | ||||
|     { | ||||
|         switch ($frequency) { | ||||
|             case RecurringInvoice::FREQUENCY_DAILY: | ||||
| @ -1353,11 +1383,56 @@ class SubscriptionService | ||||
|             case RecurringInvoice::FREQUENCY_THREE_YEARS: | ||||
|                 return $date->addYears(3); | ||||
|             default: | ||||
|                 return 0; | ||||
|                 return null; | ||||
|         }         | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Handle case where no payment is required | ||||
|      * @param  Invoice       $invoice The Invoice | ||||
|      * @param  array         $bundle  The bundle array | ||||
|      * @param  ClientContact $contact The Client Contact | ||||
|      *  | ||||
|      * @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse | ||||
|      */ | ||||
|     public function handleNoPaymentFlow(Invoice $invoice, $bundle, ClientContact $contact) | ||||
|     { | ||||
| 
 | ||||
|         if (strlen($this->subscription->recurring_product_ids) >= 1) { | ||||
| 
 | ||||
|             $recurring_invoice = $this->convertInvoiceToRecurringBundle($contact->client_id, collect($bundle)->map(function ($bund){ return (object) $bund;})); | ||||
| 
 | ||||
|             /* Start the recurring service */ | ||||
|             $recurring_invoice->service() | ||||
|                               ->start() | ||||
|                               ->save(); | ||||
| 
 | ||||
|             $invoice->recurring_id = $recurring_invoice->id; | ||||
|             $invoice->save(); | ||||
| 
 | ||||
|             $context = [ | ||||
|                 'context' => 'recurring_purchase', | ||||
|                 'recurring_invoice' => $recurring_invoice->hashed_id, | ||||
|                 'invoice' => $invoice->hashed_id, | ||||
|                 'client' => $recurring_invoice->client->hashed_id, | ||||
|                 'subscription' => $this->subscription->hashed_id, | ||||
|                 'contact' => $contact->hashed_id, | ||||
|                 'redirect_url' => "/client/recurring_invoices/{$recurring_invoice->hashed_id}", | ||||
|             ]; | ||||
| 
 | ||||
|             $this->triggerWebhook($context); | ||||
| 
 | ||||
|             return $this->handleRedirect($context['redirect_url']); | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         $redirect_url = "/client/invoices/{$invoice->hashed_id}"; | ||||
| 
 | ||||
|         return $this->handleRedirect($redirect_url); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|     * 'email' => $this->email ?? $this->contact->email, | ||||
|     * 'quantity' => $this->quantity, | ||||
|  | ||||
| @ -27,6 +27,9 @@ trait SubscriptionHooker | ||||
|             'X-Requested-With' => 'XMLHttpRequest', | ||||
|         ]; | ||||
| 
 | ||||
|         if(!isset($subscription->webhook_configuration['post_purchase_url']) && !isset($subscription->webhook_configuration['post_purchase_rest_method']))    | ||||
|             return []; | ||||
| 
 | ||||
|         if (count($subscription->webhook_configuration['post_purchase_headers']) >= 1) { | ||||
|             $headers = array_merge($headers, $subscription->webhook_configuration['post_purchase_headers']); | ||||
|         } | ||||
|  | ||||
| @ -14,8 +14,8 @@ return [ | ||||
|     'require_https' => env('REQUIRE_HTTPS', true), | ||||
|     'app_url' => rtrim(env('APP_URL', ''), '/'), | ||||
|     'app_domain' => env('APP_DOMAIN', 'invoicing.co'), | ||||
|     'app_version' => '5.5.56', | ||||
|     'app_tag' => '5.5.56', | ||||
|     'app_version' => '5.5.57', | ||||
|     'app_tag' => '5.5.57', | ||||
|     'minimum_client_version' => '5.0.16', | ||||
|     'terms_version' => '1.0.1', | ||||
|     'api_secret' => env('API_SECRET', ''), | ||||
|  | ||||
| @ -32,7 +32,7 @@ class SchedulerFactory extends Factory | ||||
|             'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY, | ||||
|             'next_run' => now()->addSeconds(rand(86400,8640000)), | ||||
|             'next_run_client' => now()->addSeconds(rand(86400,8640000)), | ||||
|             'template' => 'statement_task', | ||||
|             'template' => 'client_statement', | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -48,6 +48,7 @@ CREATE TABLE `accounts` ( | ||||
|   `account_sms_verification_number` text DEFAULT NULL, | ||||
|   `account_sms_verified` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `bank_integration_account_id` text DEFAULT NULL, | ||||
|   `is_trial` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   PRIMARY KEY (`id`), | ||||
|   KEY `accounts_payment_id_index` (`payment_id`), | ||||
|   KEY `accounts_key_index` (`key`) | ||||
| @ -255,6 +256,7 @@ CREATE TABLE `bank_transactions` ( | ||||
|   `updated_at` timestamp(6) NULL DEFAULT NULL, | ||||
|   `deleted_at` timestamp(6) NULL DEFAULT NULL, | ||||
|   `bank_transaction_rule_id` bigint(20) DEFAULT NULL, | ||||
|   `payment_id` int(10) unsigned DEFAULT NULL, | ||||
|   PRIMARY KEY (`id`), | ||||
|   KEY `bank_transactions_bank_integration_id_foreign` (`bank_integration_id`), | ||||
|   KEY `bank_transactions_user_id_foreign` (`user_id`), | ||||
| @ -519,7 +521,12 @@ CREATE TABLE `companies` ( | ||||
|   `invoice_task_project` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `report_include_deleted` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `invoice_task_lock` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `use_vendor_currency` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `matomo_url` varchar(191) DEFAULT NULL, | ||||
|   `matomo_id` bigint(20) DEFAULT NULL, | ||||
|   `convert_payment_currency` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `convert_expense_currency` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `notify_vendor_when_paid` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `invoice_task_hours` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   PRIMARY KEY (`id`), | ||||
|   UNIQUE KEY `companies_company_key_unique` (`company_key`), | ||||
|   KEY `companies_industry_id_foreign` (`industry_id`), | ||||
| @ -1139,6 +1146,7 @@ CREATE TABLE `invoices` ( | ||||
|   `paid_to_date` decimal(20,6) NOT NULL DEFAULT 0.000000, | ||||
|   `subscription_id` int(10) unsigned DEFAULT NULL, | ||||
|   `auto_bill_tries` smallint(6) NOT NULL DEFAULT 0, | ||||
|   `is_proforma` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   PRIMARY KEY (`id`), | ||||
|   UNIQUE KEY `invoices_company_id_number_unique` (`company_id`,`number`), | ||||
|   KEY `invoices_user_id_foreign` (`user_id`), | ||||
| @ -1948,20 +1956,23 @@ DROP TABLE IF EXISTS `schedulers`; | ||||
| /*!40101 SET character_set_client = utf8 */; | ||||
| CREATE TABLE `schedulers` ( | ||||
|   `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, | ||||
|   `paused` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `is_deleted` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `repeat_every` varchar(191) NOT NULL, | ||||
|   `start_from` timestamp NULL DEFAULT NULL, | ||||
|   `scheduled_run` timestamp NULL DEFAULT NULL, | ||||
|   `company_id` bigint(20) unsigned NOT NULL, | ||||
|   `created_at` timestamp NULL DEFAULT NULL, | ||||
|   `updated_at` timestamp NULL DEFAULT NULL, | ||||
|   `deleted_at` timestamp NULL DEFAULT NULL, | ||||
|   `action_name` varchar(191) NOT NULL, | ||||
|   `action_class` varchar(191) NOT NULL, | ||||
|   `parameters` mediumtext DEFAULT NULL, | ||||
|   `company_id` int(10) unsigned NOT NULL, | ||||
|   `is_paused` tinyint(1) NOT NULL DEFAULT 0, | ||||
|   `frequency_id` int(10) unsigned DEFAULT NULL, | ||||
|   `next_run` datetime DEFAULT NULL, | ||||
|   `next_run_client` datetime DEFAULT NULL, | ||||
|   `user_id` int(10) unsigned NOT NULL, | ||||
|   `name` varchar(191) NOT NULL, | ||||
|   `template` varchar(191) NOT NULL, | ||||
|   PRIMARY KEY (`id`), | ||||
|   KEY `schedulers_action_name_index` (`action_name`) | ||||
|   UNIQUE KEY `schedulers_company_id_name_unique` (`company_id`,`name`), | ||||
|   KEY `schedulers_company_id_deleted_at_index` (`company_id`,`deleted_at`), | ||||
|   CONSTRAINT `schedulers_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE | ||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; | ||||
| /*!40101 SET character_set_client = @saved_cs_client */; | ||||
| DROP TABLE IF EXISTS `sizes`; | ||||
| @ -2522,3 +2533,10 @@ INSERT INTO `migrations` VALUES (171,'2022_11_06_215526_drop_html_backups_column | ||||
| INSERT INTO `migrations` VALUES (172,'2022_11_13_034143_bank_transaction_rules_table',1); | ||||
| INSERT INTO `migrations` VALUES (173,'2022_11_16_093535_calmness_design',1); | ||||
| INSERT INTO `migrations` VALUES (174,'2022_11_22_215618_lock_tasks_when_invoiced',1); | ||||
| INSERT INTO `migrations` VALUES (175,'2022_05_12_56879_add_stripe_klarna',2); | ||||
| INSERT INTO `migrations` VALUES (176,'2022_07_12_45766_add_matomo',2); | ||||
| INSERT INTO `migrations` VALUES (177,'2022_11_30_063229_add_payment_id_to_bank_transaction_table',2); | ||||
| INSERT INTO `migrations` VALUES (178,'2022_12_07_024625_add_properties_to_companies_table',2); | ||||
| INSERT INTO `migrations` VALUES (179,'2022_12_14_004639_vendor_currency_update',2); | ||||
| INSERT INTO `migrations` VALUES (180,'2022_12_20_063038_set_proforma_invoice_type',2); | ||||
| INSERT INTO `migrations` VALUES (181,'2023_01_12_125540_set_auto_bill_on_regular_invoice_setting',2); | ||||
							
								
								
									
										108
									
								
								phpunit.yml
									
									
									
									
									
								
							
							
						
						
									
										108
									
								
								phpunit.yml
									
									
									
									
									
								
							| @ -1,108 +0,0 @@ | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - v5-develop | ||||
|   pull_request: | ||||
|     branches: | ||||
|       - v5-develop | ||||
| 
 | ||||
| name: phpunit | ||||
| jobs: | ||||
|   run: | ||||
|     runs-on: ${{ matrix.operating-system }} | ||||
|     strategy: | ||||
|       matrix: | ||||
|         operating-system: ['ubuntu-18.04', 'ubuntu-20.04'] | ||||
|         php-versions: ['7.3','7.4','8.0'] | ||||
|         phpunit-versions: ['latest'] | ||||
| 
 | ||||
|     env: | ||||
|       DB_DATABASE1: ninja | ||||
|       DB_USERNAME1: root | ||||
|       DB_PASSWORD1: ninja | ||||
|       DB_HOST1: '127.0.0.1' | ||||
|       DB_DATABASE: ninja | ||||
|       DB_USERNAME: root | ||||
|       DB_PASSWORD: ninja | ||||
|       DB_HOST: '127.0.0.1' | ||||
|       BROADCAST_DRIVER: log | ||||
|       CACHE_DRIVER: file | ||||
|       QUEUE_CONNECTION: sync | ||||
|       SESSION_DRIVER: file | ||||
|       NINJA_ENVIRONMENT: hosted | ||||
|       MULTI_DB_ENABLED: false | ||||
|       NINJA_LICENSE: 123456 | ||||
|       TRAVIS: true | ||||
|       MAIL_MAILER: log | ||||
| 
 | ||||
|     services: | ||||
|       mariadb: | ||||
|         image: mariadb:latest | ||||
|         ports: | ||||
|           - 32768:3306 | ||||
|         env: | ||||
|           MYSQL_ALLOW_EMPTY_PASSWORD: yes | ||||
|           MYSQL_USER: ninja | ||||
|           MYSQL_PASSWORD: ninja | ||||
|           MYSQL_DATABASE: ninja | ||||
|           MYSQL_ROOT_PASSWORD: ninja | ||||
|         options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3 | ||||
| 
 | ||||
|     steps: | ||||
|     - name: Start mysql service | ||||
|       run: | | ||||
|         sudo systemctl start mysql.service | ||||
|     - name: Verify MariaDB connection | ||||
|       env: | ||||
|         DB_PORT: ${{ job.services.mariadb.ports[3306] }} | ||||
|         DB_PORT1: ${{ job.services.mariadb.ports[3306] }} | ||||
| 
 | ||||
|       run: | | ||||
|         while ! mysqladmin ping -h"127.0.0.1" -P"$DB_PORT" --silent; do | ||||
|           sleep 1 | ||||
|         done | ||||
|     - name: Setup PHP | ||||
|       uses: shivammathur/setup-php@v2 | ||||
|       with: | ||||
|         php-version: ${{ matrix.php-versions }} | ||||
|         extensions: mysql, mysqlnd, sqlite3, bcmath, gmp, gd, curl, zip, openssl, mbstring, xml | ||||
| 
 | ||||
|     - uses: actions/checkout@v1 | ||||
|       with: | ||||
|         ref: v5-develop | ||||
|         fetch-depth: 1 | ||||
| 
 | ||||
|     - name: Copy .env | ||||
|       run: | | ||||
|         cp .env.ci .env | ||||
|     - name: Install composer dependencies | ||||
|       run: | | ||||
|         composer config -g github-oauth.github.com ${{ secrets.GITHUB_TOKEN }} | ||||
|         composer install | ||||
|     - name: Prepare Laravel Application | ||||
|       run: | | ||||
|         php artisan key:generate | ||||
|         php artisan optimize | ||||
|         php artisan cache:clear | ||||
|         php artisan config:cache | ||||
|     - name: Create DB and schemas | ||||
|       run: | | ||||
|         mkdir -p database | ||||
|         touch database/database.sqlite | ||||
|     - name: Migrate Database | ||||
|       run: | | ||||
|         php artisan migrate:fresh --seed --force && php artisan db:seed --force | ||||
|     - name: Prepare JS/CSS assets | ||||
|       run: | | ||||
|         npm i | ||||
|         npm run production | ||||
|     - name: Run Testsuite | ||||
|       run: | | ||||
|         cat .env | ||||
|         vendor/bin/phpunit --testdox | ||||
|       env: | ||||
|         DB_PORT: ${{ job.services.mysql.ports[3306] }} | ||||
| 
 | ||||
|     - name: Run php-cs-fixer | ||||
|       run: | | ||||
|         vendor/bin/php-cs-fixer fix | ||||
| @ -51,17 +51,33 @@ | ||||
|                           </div> | ||||
|                           <p class="mt-1 text-sm text-gray-500"></p> | ||||
|                         </div> | ||||
|                         <div class="flex content-end text-sm mt-1"> | ||||
|                         <div class="flex justify-between text-sm mt-1"> | ||||
|                             @if($subscription->per_seat_enabled) | ||||
|                             <p class="text-gray-500 w-full"></p> | ||||
|                             <p class="text-gray-500 w-3/4"></p> | ||||
|                             <div class="flex place-content-end"> | ||||
|                                 @if($subscription->use_inventory_management && $product->in_stock_quantity == 0) | ||||
|                                 <p class="text-sm font-light text-red-500 text-right mr-2 mt-2">Out of stock</p> | ||||
|                                 @else | ||||
|                                 <p class="text-sm font-light text-gray-700 text-right mr-2 mt-2">{{ ctrans('texts.qty') }}</p> | ||||
| 
 | ||||
|                                     <select wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm"> | ||||
|                                 @endif | ||||
|                                 <select wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm"  | ||||
|                                     @if($subscription->use_inventory_management && $product->in_stock_quantity == 0) | ||||
|                                     disabled  | ||||
|                                     @endif | ||||
|                                     > | ||||
|                                     <option value="1" selected="selected">1</option> | ||||
|                                         @for ($i = 2; $i <= $subscription->max_seats_limit; $i++) | ||||
| 
 | ||||
|                                     @if($subscription->max_seats_limit > 1) | ||||
|                                     { | ||||
|                                         @for ($i = 2; $i <= ($subscription->use_inventory_management ? min($subscription->max_seats_limit,$product->in_stock_quantity) : $subscription->max_seats_limit); $i++) | ||||
|                                         <option value="{{$i}}">{{$i}}</option> | ||||
|                                         @endfor | ||||
|                                     } | ||||
|                                     @else | ||||
|                                         @for ($i = 2; $i <= ($subscription->use_inventory_management ? min($product->in_stock_quantity, max(100,$product->custom_value2)) : max(100,$product->custom_value2)); $i++) | ||||
|                                         <option value="{{$i}}">{{$i}}</option> | ||||
|                                         @endfor | ||||
|                                     @endif | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                             @endif | ||||
|  | ||||
| @ -60,7 +60,7 @@ | ||||
|                             {{ $payment->translatedType() }} | ||||
|                         </td> | ||||
|                         <td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500"> | ||||
|                             {!! \App\Utils\Number::formatMoney($payment->amount, $payment->client) !!} | ||||
|                             {!! \App\Utils\Number::formatMoney($payment->amount > 0 ? $payment->amount : $payment->credits->sum('pivot.amount'), $payment->client) !!} | ||||
|                         </td> | ||||
|                         <td class="px-6 py-4 whitespace-nowrap text-sm leading-5 text-gray-500"> | ||||
|                             {{ \Illuminate\Support\Str::limit($payment->transaction_reference, 35) }} | ||||
|  | ||||
| @ -66,7 +66,7 @@ | ||||
|                                 {{ ctrans('texts.amount') }} | ||||
|                             </dt> | ||||
|                             <dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2"> | ||||
|                                 {{ $payment->formattedAmount() }} | ||||
|                                 {{ $payment->formatAmount($payment->amount > 0 ? $payment->amount : $payment?->invoices->sum('pivot.amount')) }} | ||||
|                             </dd> | ||||
|                         </div> | ||||
|                     @endif | ||||
| @ -116,6 +116,7 @@ | ||||
|                                    href="{{ route('client.invoice.show', ['invoice' => $invoice->hashed_id])}}"> | ||||
|                                     {{ $invoice->number }} | ||||
|                                 </a> | ||||
|                                 - {{ \App\Utils\Number::formatMoney($payment->invoices->where('id', $invoice->id)->sum('pivot.amount') - $payment->invoices->where('id', $invoice->id)->sum('pivot.refunded'), $payment->client) }} | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     @endforeach | ||||
|  | ||||
| @ -12,18 +12,11 @@ | ||||
| 
 | ||||
| namespace Tests\Feature\Account; | ||||
| 
 | ||||
| use App\DataMapper\ClientSettings; | ||||
| use App\DataMapper\ClientRegistrationFields; | ||||
| use App\DataMapper\CompanySettings; | ||||
| use App\Http\Livewire\CreditsTable; | ||||
| use App\Models\Account; | ||||
| use App\Models\Client; | ||||
| use App\Models\ClientContact; | ||||
| use App\Models\Company; | ||||
| use App\Models\Credit; | ||||
| use App\Models\User; | ||||
| use App\Utils\Traits\AppSetup; | ||||
| use Faker\Factory; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
| use App\Utils\Ninja; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Livewire\Livewire; | ||||
| use Tests\MockAccountData; | ||||
| @ -31,30 +24,123 @@ use Tests\TestCase; | ||||
| 
 | ||||
| class AccountEmailQuotaTest extends TestCase | ||||
| { | ||||
|     use DatabaseTransactions; | ||||
|     use AppSetup; | ||||
|     use MockAccountData; | ||||
| 
 | ||||
|     protected function setUp(): void | ||||
|     { | ||||
|         parent::setUp(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testIfQuotaBreached() | ||||
|     { | ||||
| 
 | ||||
|         config([ | ||||
|             'ninja.production' => true | ||||
|         ]); | ||||
| 
 | ||||
|         $account = Account::factory()->create([ | ||||
|             'hosted_client_count' => 1000, | ||||
|             'hosted_company_count' => 1000, | ||||
|             'is_flagged' => false, | ||||
|             'key' => '123ifyouknowwhatimean', | ||||
|             'created_at' => now(), | ||||
|             'updated_at' => now(), | ||||
|         ]); | ||||
| 
 | ||||
|         $account->num_users = 3; | ||||
|         $account->save(); | ||||
| 
 | ||||
|         $company = Company::factory()->create([ | ||||
|             'account_id' => $account->id, | ||||
|         ]); | ||||
| 
 | ||||
|         $company->client_registration_fields = ClientRegistrationFields::generate(); | ||||
| 
 | ||||
|         $settings = CompanySettings::defaults(); | ||||
| 
 | ||||
|         $settings->company_logo = 'https://pdf.invoicing.co/favicon-v2.png'; | ||||
|         $settings->website = 'www.invoiceninja.com'; | ||||
|         $settings->address1 = 'Address 1'; | ||||
|         $settings->address2 = 'Address 2'; | ||||
|         $settings->city = 'City'; | ||||
|         $settings->state = 'State'; | ||||
|         $settings->postal_code = 'Postal Code'; | ||||
|         $settings->phone = '555-343-2323'; | ||||
|         $settings->email = 'nothingtoofancy@acme.com'; | ||||
|         $settings->country_id = '840'; | ||||
|         $settings->vat_number = 'vat number'; | ||||
|         $settings->id_number = 'id number'; | ||||
|         $settings->use_credits_payment = 'always'; | ||||
|         $settings->timezone_id = '1'; | ||||
|         $settings->entity_send_time = 0; | ||||
| 
 | ||||
|         $company->track_inventory = true; | ||||
|         $company->settings = $settings; | ||||
|         $company->save(); | ||||
| 
 | ||||
|         $account->default_company_id = $company->id; | ||||
|         $account->save(); | ||||
| 
 | ||||
| 
 | ||||
|         Cache::put($account->key, 3000); | ||||
| 
 | ||||
|         $this->assertFalse($account->isPaid()); | ||||
|         $this->assertTrue(Ninja::isNinja()); | ||||
|         $this->assertEquals(20, $account->getDailyEmailLimit()); | ||||
| 
 | ||||
|         $this->assertEquals(3000, Cache::get($account->key)); | ||||
|         $this->assertTrue($account->emailQuotaExceeded()); | ||||
| 
 | ||||
|         Cache::forget('123ifyouknowwhatimean'); | ||||
| 
 | ||||
|         $this->faker = Factory::create(); | ||||
|         $this->buildCache(true); | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testQuotaValidRule() | ||||
|     { | ||||
|         Cache::increment($this->account->key); | ||||
| 
 | ||||
|         $this->assertFalse($this->account->emailQuotaExceeded()); | ||||
|         $account = Account::factory()->create([ | ||||
|             'hosted_client_count' => 1000, | ||||
|             'hosted_company_count' => 1000, | ||||
|             'is_flagged' => false, | ||||
|             'key' => '123ifyouknowwhatimean', | ||||
|             'created_at' => now(), | ||||
|             'updated_at' => now(), | ||||
|         ]); | ||||
| 
 | ||||
|         $account->num_users = 3; | ||||
|         $account->save(); | ||||
| 
 | ||||
|         Cache::increment($account->key); | ||||
| 
 | ||||
|         $this->assertFalse($account->emailQuotaExceeded()); | ||||
| 
 | ||||
|         Cache::forget('123ifyouknowwhatimean'); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testQuotaInValidRule() | ||||
|     public function testEmailSentCount() | ||||
|     { | ||||
|         Cache::increment($this->account->key, 3000); | ||||
|         $account = Account::factory()->create([ | ||||
|             'hosted_client_count' => 1000, | ||||
|             'hosted_company_count' => 1000, | ||||
|             'is_flagged' => false, | ||||
|             'key' => '123ifyouknowwhatimean', | ||||
|             'created_at' => now(), | ||||
|             'updated_at' => now(), | ||||
|         ]); | ||||
| 
 | ||||
|         $account->num_users = 3; | ||||
|         $account->save(); | ||||
| 
 | ||||
| 
 | ||||
|         Cache::put($account->key, 3000); | ||||
| 
 | ||||
|         $count = $account->emailsSent(); | ||||
| 
 | ||||
|         $this->assertEquals(3000, $count); | ||||
| 
 | ||||
|         Cache::forget('123ifyouknowwhatimean'); | ||||
| 
 | ||||
|         $this->assertTrue($this->account->emailQuotaExceeded()); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -15,7 +15,6 @@ use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Database\Eloquent\Model; | ||||
| use Illuminate\Foundation\Testing\DatabaseTransactions; | ||||
| use Illuminate\Support\Facades\Session; | ||||
| use Illuminate\Validation\ValidationException; | ||||
| use Tests\MockAccountData; | ||||
| use Tests\TestCase; | ||||
| 
 | ||||
| @ -42,6 +41,17 @@ class BankTransactionApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testBankTransactionGetClientStatus() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/bank_transactions?client_status=unmatched'.$this->encodePrimaryKey($this->bank_transaction->id)); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testBankTransactionGet() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|  | ||||
| @ -48,6 +48,19 @@ class CompanyTokenApiTest extends TestCase | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testCompanyTokenListFilter() | ||||
|     { | ||||
|         $this->withoutMiddleware(PasswordProtection::class); | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|             'X-API-PASSWORD' => 'ALongAndBriliantPassword', | ||||
|         ])->get('/api/v1/tokens?filter=xx'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testCompanyTokenList() | ||||
|     { | ||||
|         $this->withoutMiddleware(PasswordProtection::class); | ||||
|  | ||||
| @ -40,6 +40,16 @@ class CreditTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testCreditGetClientStatus() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/credits?client_status=draft'.$this->encodePrimaryKey($this->bank_transaction->id)); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testCreditsList() | ||||
|     { | ||||
|         Client::factory()->count(3)->create(['user_id' => $this->user->id, 'company_id' => $this->company->id])->each(function ($c) { | ||||
|  | ||||
							
								
								
									
										212
									
								
								tests/Feature/Email/EmailServiceTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								tests/Feature/Email/EmailServiceTest.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,212 @@ | ||||
| <?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 Tests\Feature\Email; | ||||
| 
 | ||||
| use App\Services\Email\EmailObject; | ||||
| use App\Services\Email\EmailService; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Routing\Middleware\ThrottleRequests; | ||||
| use Tests\MockAccountData; | ||||
| use Tests\TestCase; | ||||
| use Illuminate\Mail\Mailables\Address; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| 
 | ||||
| /** | ||||
|  * @test | ||||
|  * @covers  App\Services\Email\EmailService | ||||
|  */ | ||||
| class EmailServiceTest extends TestCase | ||||
| { | ||||
|     use MakesHash; | ||||
|     use GeneratesCounter; | ||||
|     use MockAccountData; | ||||
| 
 | ||||
|     public EmailService $email_service; | ||||
| 
 | ||||
|     public EmailObject $email_object; | ||||
| 
 | ||||
|     protected function setUp() :void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         if(!class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) | ||||
|             $this->markTestSkipped('Skipped :: test not needed in this environment'); | ||||
| 
 | ||||
|         $this->makeTestData(); | ||||
| 
 | ||||
|         $this->email_object = new EmailObject(); | ||||
|         $this->email_object->to = [new Address("testing@gmail.com", "Cool Name")]; | ||||
|         $this->email_object->attachments = []; | ||||
|         $this->email_object->settings = $this->client->getMergedSettings(); | ||||
|         $this->email_object->company = $this->client->company; | ||||
|         $this->email_object->client = $this->client; | ||||
|         $this->email_object->email_template_subject = 'email_subject_statement'; | ||||
|         $this->email_object->email_template_body = 'email_template_statement'; | ||||
|         $this->email_object->variables = [ | ||||
|             '$client' => $this->client->present()->name(), | ||||
|             '$start_date' => '2022-01-01', | ||||
|             '$end_date' => '2023-01-01', | ||||
|         ]; | ||||
| 
 | ||||
|         $this->email_service = new EmailService($this->email_object, $this->company); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testScanEmailsAttemptedFromVerifiedAccounts() | ||||
|     { | ||||
|         $email_filter = new \Modules\Admin\Jobs\Account\EmailFilter($this->email_object, $this->client->company); | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         $this->account->account_sms_verified = true; | ||||
|         $this->account->is_verified_account = false; | ||||
|         $this->account->save(); | ||||
| 
 | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|         collect($email_filter->getSpamKeywords())->each(function ($spam_subject){ | ||||
| 
 | ||||
|             $this->email_object->subject = $spam_subject; | ||||
| 
 | ||||
|             $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public function scanEmailsAttemptedFromUnverifiedAccounts() | ||||
|     { | ||||
| 
 | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|         $this->account->account_sms_verified = false; | ||||
|         $this->account->save(); | ||||
| 
 | ||||
|         $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testVerifiedAccountsSkipFilters() | ||||
|     { | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|         $this->account->is_verified_account = true; | ||||
|         $this->account->save(); | ||||
| 
 | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testClientMailersAreUnCapped() | ||||
|     { | ||||
| 
 | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1000000); | ||||
| 
 | ||||
|         collect([ | ||||
|             'gmail',  | ||||
|             'office365',  | ||||
|             'client_postmark',  | ||||
|             'client_mailgun']) | ||||
|         ->each(function ($mailer){ | ||||
| 
 | ||||
|             $this->email_object->settings->email_sending_method = $mailer; | ||||
| 
 | ||||
|             $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|         $this->email_object->settings->email_sending_method = 'postmark'; | ||||
| 
 | ||||
|         $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testFlaggedInvalidEmailsPrevented() | ||||
|     { | ||||
| 
 | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|             $this->email_object->to = [new Address("user@example.com", "Cool Name")]; | ||||
| 
 | ||||
|             $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
| 
 | ||||
|         collect([ | ||||
|             'user@example.com', | ||||
|             '', | ||||
|             'bademail', | ||||
|             'domain.com', | ||||
|         ])->each(function ($email){ | ||||
| 
 | ||||
| 
 | ||||
|             $this->email_object->to = [new Address($email, "Cool Name")]; | ||||
| 
 | ||||
|             $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|         }); | ||||
| 
 | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     public function testFlaggedAccountsPrevented() | ||||
|     { | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
| 
 | ||||
|         $this->account->is_flagged = true; | ||||
|         $this->account->save(); | ||||
| 
 | ||||
|         $this->assertTrue($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testPreFlightChecksHosted() | ||||
|     { | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
|      | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
|      | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testPreFlightChecksSelfHost() | ||||
|     { | ||||
| 
 | ||||
|         Cache::put($this->account->key, 1); | ||||
| 
 | ||||
|         config(['ninja.environment' => 'selfhost']); | ||||
|      | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -1,84 +0,0 @@ | ||||
| <?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 Tests\Feature; | ||||
| 
 | ||||
| use App\Services\Email\EmailObject; | ||||
| use App\Services\Email\EmailService; | ||||
| use App\Utils\Traits\GeneratesCounter; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Illuminate\Routing\Middleware\ThrottleRequests; | ||||
| use Tests\MockAccountData; | ||||
| use Tests\TestCase; | ||||
| use Illuminate\Mail\Mailables\Address; | ||||
| 
 | ||||
| /** | ||||
|  * @test | ||||
|  * @covers  App\Services\Email\EmailService | ||||
|  */ | ||||
| class EmailTest extends TestCase | ||||
| { | ||||
|     use MakesHash; | ||||
|     use GeneratesCounter; | ||||
|     use MockAccountData; | ||||
| 
 | ||||
|     public EmailService $email_service; | ||||
| 
 | ||||
|     public EmailObject $email_object; | ||||
| 
 | ||||
|     protected function setUp() :void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         if(!class_exists(\Modules\Admin\Jobs\Account\EmailFilter::class)) | ||||
|             $this->markTestSkipped('Skip test not needed in this environment'); | ||||
| 
 | ||||
|         $this->makeTestData(); | ||||
| 
 | ||||
|         $this->email_object = new EmailObject(); | ||||
|         $this->email_object->to = [new Address("testing@gmail.com", "Cool Name")]; | ||||
|         $this->email_object->attachments = []; | ||||
|         $this->email_object->settings = $this->client->getMergedSettings(); | ||||
|         $this->email_object->company = $this->client->company; | ||||
|         $this->email_object->client = $this->client; | ||||
|         $this->email_object->email_template_subject = 'email_subject_statement'; | ||||
|         $this->email_object->email_template_body = 'email_template_statement'; | ||||
|         $this->email_object->variables = [ | ||||
|             '$client' => $this->client->present()->name(), | ||||
|             '$start_date' => '2022-01-01', | ||||
|             '$end_date' => '2023-01-01', | ||||
|         ]; | ||||
| 
 | ||||
|         $this->email_service = new EmailService($this->email_object, $this->company); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testPreFlightChecksHosted() | ||||
|     { | ||||
|      | ||||
|         config(['ninja.environment' => 'hosted']); | ||||
|      | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testPreFlightChecksSelfHost() | ||||
|     { | ||||
|      | ||||
|         config(['ninja.environment' => 'selfhost']); | ||||
|      | ||||
|         $this->assertFalse($this->email_service->preFlightChecksFail()); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| @ -41,6 +41,18 @@ class ExpenseApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testExpenseGetClientStatus() | ||||
|     { | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/expenses?client_status=paid'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testExpensePost() | ||||
|     { | ||||
|         $data = [ | ||||
|  | ||||
| @ -66,6 +66,8 @@ class ProductSalesReportTest extends TestCase | ||||
| 
 | ||||
|     public $account; | ||||
| 
 | ||||
|     public $client; | ||||
| 
 | ||||
|     /** | ||||
|      *      start_date - Y-m-d | ||||
|             end_date - Y-m-d | ||||
| @ -108,6 +110,9 @@ class ProductSalesReportTest extends TestCase | ||||
|             'settings' => $settings, | ||||
|         ]); | ||||
| 
 | ||||
|         $this->company->settings = $settings; | ||||
|         $this->company->save(); | ||||
| 
 | ||||
|         $this->payload = [ | ||||
|             'start_date' => '2000-01-01', | ||||
|             'end_date' => '2030-01-11', | ||||
| @ -115,6 +120,13 @@ class ProductSalesReportTest extends TestCase | ||||
|             'is_income_billed' => true, | ||||
|             'include_tax' => false, | ||||
|         ]; | ||||
| 
 | ||||
|         $this->client = Client::factory()->create([ | ||||
|             'user_id' => $this->user->id, | ||||
|             'company_id' => $this->company->id, | ||||
|             'is_deleted' => 0, | ||||
|         ]); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testProductSalesInstance() | ||||
| @ -133,22 +145,16 @@ class ProductSalesReportTest extends TestCase | ||||
|         $this->buildData(); | ||||
| 
 | ||||
| 
 | ||||
|         $client = Client::factory()->create([ | ||||
|             'user_id' => $this->user->id, | ||||
|             'company_id' => $this->company->id, | ||||
|             'is_deleted' => 0, | ||||
|         ]); | ||||
| 
 | ||||
|         $this->payload = [ | ||||
|             'start_date' => '2000-01-01', | ||||
|             'end_date' => '2030-01-11', | ||||
|             'date_range' => 'custom', | ||||
|             'client_id' => $client->id, | ||||
|             'client_id' => $this->client->id, | ||||
|             'report_keys' => [] | ||||
|         ]; | ||||
| 
 | ||||
|         $i = Invoice::factory()->create([ | ||||
|             'client_id' => $client->id, | ||||
|             'client_id' => $this->client->id, | ||||
|             'user_id' => $this->user->id, | ||||
|             'company_id' => $this->company->id, | ||||
|             'amount' => 0, | ||||
| @ -174,7 +180,6 @@ class ProductSalesReportTest extends TestCase | ||||
|         $response = $pl->run(); | ||||
| 
 | ||||
|         $this->assertIsString($response); | ||||
| // nlog($response);
 | ||||
| 
 | ||||
|         $this->account->delete(); | ||||
|     } | ||||
|  | ||||
| @ -22,6 +22,7 @@ use App\Models\Product; | ||||
| use App\Models\TaxRate; | ||||
| use App\Models\Vendor; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use App\Utils\TruthSource; | ||||
| use Illuminate\Routing\Middleware\ThrottleRequests; | ||||
| use Illuminate\Support\Facades\Cache; | ||||
| use Illuminate\Support\Str; | ||||
| @ -50,6 +51,9 @@ class CsvImportTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
| 
 | ||||
|         $this->withoutExceptionHandling(); | ||||
| 
 | ||||
|         auth()->login($this->user); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testExpenseCsvImport() | ||||
| @ -274,6 +278,11 @@ class CsvImportTest extends TestCase | ||||
| 
 | ||||
|         Cache::put($hash.'-invoice', base64_encode($csv), 360); | ||||
| 
 | ||||
|         $truth = app()->make(TruthSource::class); | ||||
|         $truth->setCompanyUser($this->cu); | ||||
|         $truth->setUser($this->user); | ||||
|         $truth->setCompany($this->company); | ||||
| 
 | ||||
|         $csv_importer = new Csv($data, $this->company); | ||||
| 
 | ||||
|         $csv_importer->import('invoice'); | ||||
|  | ||||
| @ -40,6 +40,9 @@ class InvitationTest extends TestCase | ||||
|     protected function setUp() :void | ||||
|     { | ||||
|         parent::setUp(); | ||||
| 
 | ||||
|         $this->faker = \Faker\Factory::create(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testInvoiceCreationAfterInvoiceMarkedSent() | ||||
| @ -52,10 +55,13 @@ class InvitationTest extends TestCase | ||||
|         $account->default_company_id = $company->id; | ||||
|         $account->save(); | ||||
| 
 | ||||
|         $user = User::where('email', 'user@example.com')->first(); | ||||
|         $fake_email = $this->faker->email(); | ||||
| 
 | ||||
|         $user = User::where('email', $fake_email)->first(); | ||||
| 
 | ||||
|         if (! $user) { | ||||
|             $user = User::factory()->create([ | ||||
|                 'email' => $fake_email, | ||||
|                 'account_id' => $account->id, | ||||
|                 'confirmation_code' => $this->createDbHash(config('database.default')), | ||||
|             ]); | ||||
|  | ||||
| @ -47,6 +47,16 @@ class InvoiceTest extends TestCase | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testInvoiceGetPaidInvoices() | ||||
|     { | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/invoices?client_status=paid',) | ||||
|         ->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testInvoiceArchiveAction() | ||||
|     { | ||||
| 
 | ||||
|  | ||||
| @ -48,6 +48,17 @@ class PaymentTermsApiTest extends TestCase | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testPaymentTermsGetWithFilter() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/payment_terms?filter=hey'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testPaymentTermsGet() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|  | ||||
| @ -62,6 +62,18 @@ class PaymentTest extends TestCase | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetPaymentMatchList() | ||||
|     { | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/payments?match_transactions=true') | ||||
|           ->assertStatus(200); | ||||
| 
 | ||||
|          | ||||
|     } | ||||
| 
 | ||||
|     public function testStorePaymentIdempotencyKeyIllegalLength() | ||||
|     { | ||||
|         $client = ClientFactory::create($this->company->id, $this->user->id); | ||||
|  | ||||
| @ -47,6 +47,15 @@ class ProductTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testProductGetProductKeyFilter() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/products?product_key=xx') | ||||
|         ->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testProductList() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|  | ||||
| @ -42,6 +42,16 @@ class ProjectApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testProjectGetFilter() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/projects?filter=xx'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testProjectGet() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|  | ||||
| @ -40,6 +40,16 @@ class PurchaseOrderTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testPurchaseOrderGetWithClientStatus() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/purchase_orders?client_status=sent'.$this->encodePrimaryKey($this->purchase_order->id)); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testPostNewPurchaseOrderPdf() | ||||
|     { | ||||
|         $purchase_order = [ | ||||
|  | ||||
| @ -50,6 +50,15 @@ class QuoteTest extends TestCase | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testQuoteListApproved() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/quotes?client_status=approved'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testQuoteConvertToProject() | ||||
|  | ||||
| @ -43,6 +43,16 @@ class RecurringExpenseApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testRecurringExpenseGetFiltered() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/recurring_expenses?filter=xx'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testRecurringExpenseGet() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|  | ||||
| @ -52,6 +52,17 @@ class RecurringInvoiceTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testRecurringGetStatus() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/recurring_invoices?client_status=active') | ||||
|           ->assertStatus(200); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testPostRecurringInvoiceWithPlaceholderVariables() | ||||
|     { | ||||
|         $line_items = []; | ||||
|  | ||||
| @ -47,6 +47,17 @@ class RecurringQuoteTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
|     public function testRecurringQuoteListFilter() | ||||
|     { | ||||
| 
 | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/recurring_quotes?filter=xx'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testRecurringQuoteList() | ||||
|     { | ||||
|         RecurringQuote::factory()->create(['user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id]); | ||||
|  | ||||
| @ -14,6 +14,7 @@ namespace Tests\Feature\Scheduler; | ||||
| use App\Factory\SchedulerFactory; | ||||
| use App\Models\Client; | ||||
| use App\Models\RecurringInvoice; | ||||
| use App\Models\Scheduler; | ||||
| use App\Services\Scheduler\SchedulerService; | ||||
| use App\Utils\Traits\MakesHash; | ||||
| use Carbon\Carbon; | ||||
| @ -53,6 +54,72 @@ class SchedulerTest extends TestCase | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testClientCountResolution() | ||||
|     { | ||||
| 
 | ||||
|         $c = Client::factory()->create([ | ||||
|             'company_id' => $this->company->id, | ||||
|             'user_id' => $this->user->id, | ||||
|             'number' => rand(1000,100000), | ||||
|             'name' => 'A fancy client' | ||||
|         ]); | ||||
| 
 | ||||
|         $c2 = Client::factory()->create([ | ||||
|             'company_id' => $this->company->id, | ||||
|             'user_id' => $this->user->id, | ||||
|             'number' => rand(1000,100000), | ||||
|             'name' => 'A fancy client' | ||||
|         ]); | ||||
| 
 | ||||
|         $data = [ | ||||
|             'name' => 'A test statement scheduler', | ||||
|             'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY, | ||||
|             'next_run' => now()->format('Y-m-d'), | ||||
|             'template' => 'client_statement', | ||||
|             'parameters' => [ | ||||
|                 'date_range' => 'previous_month', | ||||
|                 'show_payments_table' => true, | ||||
|                 'show_aging_table' => true, | ||||
|                 'status' => 'paid', | ||||
|                 'clients' => [ | ||||
|                     $c2->hashed_id,  | ||||
|                     $c->hashed_id | ||||
|                 ], | ||||
|             ], | ||||
|         ]; | ||||
| 
 | ||||
|         $response = false; | ||||
| 
 | ||||
|         try{ | ||||
|             $response = $this->withHeaders([ | ||||
|                 'X-API-SECRET' => config('ninja.api_secret'), | ||||
|                 'X-API-TOKEN' => $this->token, | ||||
|             ])->postJson('/api/v1/task_schedulers', $data); | ||||
| 
 | ||||
|         } catch (ValidationException $e) { | ||||
|             $message = json_decode($e->validator->getMessageBag(), 1); | ||||
|             nlog($message); | ||||
|         } | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
| 
 | ||||
|         $data = $response->json(); | ||||
| 
 | ||||
|         $scheduler = Scheduler::find($this->decodePrimaryKey($data['data']['id'])); | ||||
| 
 | ||||
|         $this->assertInstanceOf(Scheduler::class, $scheduler); | ||||
| 
 | ||||
|         $this->assertCount(2, $scheduler->parameters['clients']); | ||||
| 
 | ||||
|         $q = Client::query() | ||||
|               ->where('company_id', $scheduler->company_id) | ||||
|               ->whereIn('id', $this->transformKeys($scheduler->parameters['clients'])) | ||||
|               ->cursor(); | ||||
| 
 | ||||
|         $this->assertCount(2, $q); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testClientsValidationInScheduledTask() | ||||
|     { | ||||
| 
 | ||||
| @ -162,7 +229,7 @@ class SchedulerTest extends TestCase | ||||
|         $data = [ | ||||
|             'name' => 'A test statement scheduler', | ||||
|             'frequency_id' => RecurringInvoice::FREQUENCY_MONTHLY, | ||||
|             'next_run' => "2023-01-01", | ||||
|             'next_run' => now()->format('Y-m-d'), | ||||
|             'template' => 'client_statement', | ||||
|             'parameters' => [ | ||||
|                 'date_range' => 'previous_month', | ||||
| @ -184,7 +251,7 @@ class SchedulerTest extends TestCase | ||||
| 
 | ||||
|         $scheduler->fresh(); | ||||
| 
 | ||||
|         $this->assertEquals("2023-02-01", $scheduler->next_run->format('Y-m-d')); | ||||
|         $this->assertEquals(now()->addMonth()->format('Y-m-d'), $scheduler->next_run->format('Y-m-d')); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -50,6 +50,17 @@ class SubscriptionApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testSubscriptionFilter() | ||||
|     { | ||||
|          | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/subscriptions?filter=xx') | ||||
|           ->assertStatus(200); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testSubscriptionsGet() | ||||
|     { | ||||
|         $product = Product::factory()->create([ | ||||
|  | ||||
| @ -34,6 +34,18 @@ class SystemLogApiTest extends TestCase | ||||
|         $this->makeTestData(); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     public function testFilters() | ||||
|     { | ||||
|          | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/system_logs?type_id=3') | ||||
|         ->assertStatus(200);; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testSystemLogRoutes() | ||||
|     { | ||||
|         $sl = [ | ||||
|  | ||||
| @ -44,6 +44,17 @@ class TaskApiTest extends TestCase | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|     public function testTaskListClientStatus() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/tasks?client_status=invoiced') | ||||
|           ->assertStatus(200); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public function testTaskLockingGate() | ||||
|     { | ||||
|         $data = [ | ||||
|  | ||||
| @ -41,6 +41,16 @@ class TaskStatusApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testTaskStatusGetFilter() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/task_statuses?filter=xx'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testTaskStatusPost() | ||||
|     { | ||||
|         $data = [ | ||||
|  | ||||
| @ -42,6 +42,16 @@ class TaxRateApiTest extends TestCase | ||||
|         Model::reguard(); | ||||
|     } | ||||
| 
 | ||||
|     public function testTaxRatesGetFilter() | ||||
|     { | ||||
|         $response = $this->withHeaders([ | ||||
|             'X-API-SECRET' => config('ninja.api_secret'), | ||||
|             'X-API-TOKEN' => $this->token, | ||||
|         ])->get('/api/v1/tax_rates?filter=gst'); | ||||
| 
 | ||||
|         $response->assertStatus(200); | ||||
|     } | ||||
| 
 | ||||
|     public function testTaxRatePost() | ||||
|     { | ||||
|         $rate_name = $this->faker->firstName(); | ||||
|  | ||||
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