diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index ad1109a26bcb..5112b4f9ef94 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -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 }} \ No newline at end of file diff --git a/VERSION.txt b/VERSION.txt index cae032632b73..e0b13d4547dc 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.56 \ No newline at end of file +5.5.57 \ No newline at end of file diff --git a/app/Console/Commands/CreateSingleAccount.php b/app/Console/Commands/CreateSingleAccount.php index fb7e9b575dce..5a18fdd66277 100644 --- a/app/Console/Commands/CreateSingleAccount.php +++ b/app/Console/Commands/CreateSingleAccount.php @@ -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, diff --git a/app/Console/Commands/PostUpdate.php b/app/Console/Commands/PostUpdate.php index 007a9bed6211..73d7d1e10896 100644 --- a/app/Console/Commands/PostUpdate.php +++ b/app/Console/Commands/PostUpdate.php @@ -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'); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 7587da2e5062..352b89b181e2 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -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(); diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index e65eedfa9e75..fbf2e25ecf76 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -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', diff --git a/app/DataMapper/Schedule/ClientStatement.php b/app/DataMapper/Schedule/ClientStatement.php index 37ba76d081cb..6ab2284f771b 100644 --- a/app/DataMapper/Schedule/ClientStatement.php +++ b/app/DataMapper/Schedule/ClientStatement.php @@ -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; diff --git a/app/Filters/BankIntegrationFilters.php b/app/Filters/BankIntegrationFilters.php index 381966b35a50..9774af605711 100644 --- a/app/Filters/BankIntegrationFilters.php +++ b/app/Filters/BankIntegrationFilters.php @@ -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. diff --git a/app/Filters/BankTransactionFilters.php b/app/Filters/BankTransactionFilters.php index 96eec99db028..48d2565f9327 100644 --- a/app/Filters/BankTransactionFilters.php +++ b/app/Filters/BankTransactionFilters.php @@ -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; } - if (in_array('unmatched', $status_parameters)) { - $status_array[] = BankTransaction::STATUS_UNMATCHED; - // $this->builder->orWhere('status_id', BankTransaction::STATUS_UNMATCHED); - } + $this->builder->where(function ($query) use ($status_parameters){ - if (in_array('matched', $status_parameters)) { - $status_array[] = BankTransaction::STATUS_MATCHED; - // $this->builder->where('status_id', BankTransaction::STATUS_MATCHED); - } + $status_array = []; + + $debit_or_withdrawal_array = []; - if (in_array('converted', $status_parameters)) { - $status_array[] = BankTransaction::STATUS_CONVERTED; - // $this->builder->where('status_id', BankTransaction::STATUS_CONVERTED); - } + if (in_array('unmatched', $status_parameters)) { + $status_array[] = BankTransaction::STATUS_UNMATCHED; + } - if (in_array('deposits', $status_parameters)) { - $debit_or_withdrawal_array[] = 'CREDIT'; - // $this->builder->where('base_type', 'CREDIT'); - } + if (in_array('matched', $status_parameters)) { + $status_array[] = BankTransaction::STATUS_MATCHED; + } - if (in_array('withdrawals', $status_parameters)) { - $debit_or_withdrawal_array[] = 'DEBIT'; - // $this->builder->where('base_type', 'DEBIT'); - } + if (in_array('converted', $status_parameters)) { + $status_array[] = BankTransaction::STATUS_CONVERTED; + } - if(count($status_array) >=1) { - $this->builder->whereIn('status_id', $status_array); - } + if (in_array('deposits', $status_parameters)) { + $debit_or_withdrawal_array[] = 'CREDIT'; + } - if(count($debit_or_withdrawal_array) >=1) { - $this->builder->orWhereIn('base_type', $debit_or_withdrawal_array); - } + if (in_array('withdrawals', $status_parameters)) { + $debit_or_withdrawal_array[] = 'DEBIT'; + } + + if(count($status_array) >=1) { + $query->whereIn('status_id', $status_array); + } + + if(count($debit_or_withdrawal_array) >=1) { + $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(); } } diff --git a/app/Filters/ClientFilters.php b/app/Filters/ClientFilters.php index df4b86fdbca3..63a6598b9541 100644 --- a/app/Filters/ClientFilters.php +++ b/app/Filters/ClientFilters.php @@ -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; + + } } diff --git a/app/Filters/CreditFilters.php b/app/Filters/CreditFilters.php index df34873d438f..cd51ceabb03a 100644 --- a/app/Filters/CreditFilters.php +++ b/app/Filters/CreditFilters.php @@ -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; + + if (in_array('partial', $status_parameters)) + $credit_filters[] = Credit::STATUS_PARTIAL; - //->where('due_date', '>', Carbon::now()) - //->orWhere('partial_due_date', '>', Carbon::now()); + 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; } diff --git a/app/Filters/DesignFilters.php b/app/Filters/DesignFilters.php index 08f235e932c5..76b7abb33a63 100644 --- a/app/Filters/DesignFilters.php +++ b/app/Filters/DesignFilters.php @@ -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,9 +23,10 @@ class DesignFilters extends QueryFilters * * @param string query filter * @return Builder + * * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->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 + public function sort(string $sort): Builder { $sort_col = explode('|', $sort); - return $this->builder->orderBy($sort_col[0], $sort_col[1]); - } + 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'); diff --git a/app/Filters/DocumentFilters.php b/app/Filters/DocumentFilters.php index ec7b34eb7072..a684adf93072 100644 --- a/app/Filters/DocumentFilters.php +++ b/app/Filters/DocumentFilters.php @@ -12,7 +12,6 @@ namespace App\Filters; use App\Models\Company; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** @@ -27,7 +26,7 @@ class DocumentFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -36,8 +35,15 @@ class DocumentFilters extends QueryFilters return $this->builder; } - /* If client ID passed to this entity, simply return */ - public function client_id(string $client_id = '') :Builder + /** + * 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); - return $this->builder->orderBy($sort_col[0], $sort_col[1]); + if(is_array($sort_col)) + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + + return $this->builder; } diff --git a/app/Filters/ExpenseCategoryFilters.php b/app/Filters/ExpenseCategoryFilters.php index aebfc5c91971..7b9bbd61f928 100644 --- a/app/Filters/ExpenseCategoryFilters.php +++ b/app/Filters/ExpenseCategoryFilters.php @@ -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(); } } diff --git a/app/Filters/ExpenseFilters.php b/app/Filters/ExpenseFilters.php index 2a1892e9f2a3..5eb7b8c43999 100644 --- a/app/Filters/ExpenseFilters.php +++ b/app/Filters/ExpenseFilters.php @@ -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.'%'); }); } diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index da66d3a9b792..19eb08c8289c 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -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); } diff --git a/app/Filters/PaymentFilters.php b/app/Filters/PaymentFilters.php index dc012dff3aa2..2977e072cb7f 100644 --- a/app/Filters/PaymentFilters.php +++ b/app/Filters/PaymentFilters.php @@ -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); - return $this->builder->orderBy($sort_col[0], $sort_col[1]); + if(is_array($sort_col)) + return $this->builder->orderBy($sort_col[0], $sort_col[1]); + + return true; } public function number(string $number) : Builder diff --git a/app/Filters/PaymentTermFilters.php b/app/Filters/PaymentTermFilters.php index 0352cca5f213..fb4be8c424b5 100644 --- a/app/Filters/PaymentTermFilters.php +++ b/app/Filters/PaymentTermFilters.php @@ -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. @@ -29,7 +25,7 @@ class PaymentTermFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -46,7 +42,7 @@ class PaymentTermFilters 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); @@ -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); } } diff --git a/app/Filters/ProductFilters.php b/app/Filters/ProductFilters.php index 8fa1d25264ed..1d6f524c9221 100644 --- a/app/Filters/ProductFilters.php +++ b/app/Filters/ProductFilters.php @@ -11,7 +11,6 @@ namespace App\Filters; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** diff --git a/app/Filters/ProjectFilters.php b/app/Filters/ProjectFilters.php index e489488b0cd1..030a7be215f3 100644 --- a/app/Filters/ProjectFilters.php +++ b/app/Filters/ProjectFilters.php @@ -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,7 +48,8 @@ class ProjectFilters extends QueryFilters { $sort_col = explode('|', $sort); - return $this->builder->orderBy($sort_col[0], $sort_col[1]); + if(is_array($sort_col)) + return $this->builder->orderBy($sort_col[0], $sort_col[1]); } /** diff --git a/app/Filters/PurchaseOrderFilters.php b/app/Filters/PurchaseOrderFilters.php index ad86221ff6a8..5c21080da364 100644 --- a/app/Filters/PurchaseOrderFilters.php +++ b/app/Filters/PurchaseOrderFilters.php @@ -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; } diff --git a/app/Filters/QuoteFilters.php b/app/Filters/QuoteFilters.php index c83ed856293d..c0b88a82b4da 100644 --- a/app/Filters/QuoteFilters.php +++ b/app/Filters/QuoteFilters.php @@ -12,7 +12,6 @@ namespace App\Filters; use App\Models\Quote; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** diff --git a/app/Filters/RecurringExpenseFilters.php b/app/Filters/RecurringExpenseFilters.php index bf82facccc70..f7a0872720b3 100644 --- a/app/Filters/RecurringExpenseFilters.php +++ b/app/Filters/RecurringExpenseFilters.php @@ -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. @@ -29,19 +25,18 @@ class RecurringExpenseFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; } - 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.'%'); + return $this->builder->where(function ($query) use ($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.'%'); }); } @@ -51,7 +46,7 @@ class RecurringExpenseFilters 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); diff --git a/app/Filters/RecurringInvoiceFilters.php b/app/Filters/RecurringInvoiceFilters.php index dfd0ee60e438..88a0f4ee3c41 100644 --- a/app/Filters/RecurringInvoiceFilters.php +++ b/app/Filters/RecurringInvoiceFilters.php @@ -12,7 +12,6 @@ namespace App\Filters; use App\Models\RecurringInvoice; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** @@ -77,7 +76,10 @@ class RecurringInvoiceFilters extends QueryFilters if (in_array('completed', $status_parameters)) $recurring_filters[] = RecurringInvoice::STATUS_COMPLETED; - return $this->builder->whereIn('status_id', $recurring_filters); + if(count($recurring_filters) >= 1) + return $this->builder->whereIn('status_id', $recurring_filters); + + return $this->builder; } diff --git a/app/Filters/RecurringQuoteFilters.php b/app/Filters/RecurringQuoteFilters.php index 3a1879e9ed79..1229ee4c5ae7 100644 --- a/app/Filters/RecurringQuoteFilters.php +++ b/app/Filters/RecurringQuoteFilters.php @@ -11,7 +11,6 @@ namespace App\Filters; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** @@ -26,7 +25,7 @@ class RecurringQuoteFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -46,7 +45,7 @@ class RecurringQuoteFilters 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); @@ -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(); } diff --git a/app/Filters/SubscriptionFilters.php b/app/Filters/SubscriptionFilters.php index 4ae2e1aec1fb..7c3775291119 100644 --- a/app/Filters/SubscriptionFilters.php +++ b/app/Filters/SubscriptionFilters.php @@ -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. @@ -29,7 +25,7 @@ class SubscriptionFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -46,7 +42,7 @@ class SubscriptionFilters 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); @@ -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(); } diff --git a/app/Filters/SystemLogFilters.php b/app/Filters/SystemLogFilters.php index 126c81209955..a7200d6b0911 100644 --- a/app/Filters/SystemLogFilters.php +++ b/app/Filters/SystemLogFilters.php @@ -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(); } diff --git a/app/Filters/TaskFilters.php b/app/Filters/TaskFilters.php index f4a0fb519912..43e77faff141 100644 --- a/app/Filters/TaskFilters.php +++ b/app/Filters/TaskFilters.php @@ -11,7 +11,6 @@ namespace App\Filters; -use App\Models\User; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Builder; @@ -29,7 +28,7 @@ class TaskFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -55,7 +54,7 @@ class TaskFilters extends QueryFilters * @param string client_status The invoice status as seen by the client * @return Builder */ - public function client_status(string $value = '') :Builder + public function client_status(string $value = ''): Builder { if (strlen($value) == 0) { return $this->builder; @@ -90,7 +89,7 @@ class TaskFilters 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); @@ -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(); } diff --git a/app/Filters/TaskStatusFilters.php b/app/Filters/TaskStatusFilters.php index d197b764603c..61ecf95b7b1f 100644 --- a/app/Filters/TaskStatusFilters.php +++ b/app/Filters/TaskStatusFilters.php @@ -11,7 +11,6 @@ namespace App\Filters; -use App\Models\User; use Illuminate\Database\Eloquent\Builder; /** @@ -26,7 +25,7 @@ class TaskStatusFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->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(); } diff --git a/app/Filters/TaxRateFilters.php b/app/Filters/TaxRateFilters.php index a161ccec7750..1e838b779ad6 100644 --- a/app/Filters/TaxRateFilters.php +++ b/app/Filters/TaxRateFilters.php @@ -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(); } diff --git a/app/Filters/TokenFilters.php b/app/Filters/TokenFilters.php index ac9507fb4ad9..d4cd948bcb95 100644 --- a/app/Filters/TokenFilters.php +++ b/app/Filters/TokenFilters.php @@ -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(); } diff --git a/app/Filters/UserFilters.php b/app/Filters/UserFilters.php index bded4c02eb9e..986748955228 100644 --- a/app/Filters/UserFilters.php +++ b/app/Filters/UserFilters.php @@ -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); + } + } diff --git a/app/Filters/VendorFilters.php b/app/Filters/VendorFilters.php index 914025ac0457..f723893cef76 100644 --- a/app/Filters/VendorFilters.php +++ b/app/Filters/VendorFilters.php @@ -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. @@ -29,7 +25,7 @@ class VendorFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; @@ -56,7 +52,7 @@ class VendorFilters 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); @@ -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(); } } diff --git a/app/Filters/WebhookFilters.php b/app/Filters/WebhookFilters.php index 056b1adf4818..0975efbb5ea7 100644 --- a/app/Filters/WebhookFilters.php +++ b/app/Filters/WebhookFilters.php @@ -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. @@ -29,14 +25,14 @@ class WebhookFilters extends QueryFilters * @return Builder * @deprecated */ - public function filter(string $filter = '') : Builder + public function filter(string $filter = ''): Builder { if (strlen($filter) == 0) { return $this->builder; } return $this->builder->where(function ($query) use ($filter) { - $query->where('webhooks.target_url', 'like', '%'.$filter.'%'); + $query->where('target_url', 'like', '%'.$filter.'%'); }); } @@ -46,7 +42,7 @@ class WebhookFilters 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); @@ -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(); } diff --git a/app/Helpers/ClientPortal.php b/app/Helpers/ClientPortal.php index 80c174ebf66b..f5bea3f91554 100644 --- a/app/Helpers/ClientPortal.php +++ b/app/Helpers/ClientPortal.php @@ -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; } diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 065c81ef478e..362198218d09 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -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" * ) * ) */ diff --git a/app/Http/Controllers/BankTransactionRuleController.php b/app/Http/Controllers/BankTransactionRuleController.php index 5e16a645e7e0..3386f95558dc 100644 --- a/app/Http/Controllers/BankTransactionRuleController.php +++ b/app/Http/Controllers/BankTransactionRuleController.php @@ -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", diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index 9267853d0af6..115009ccdd55 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -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); } diff --git a/app/Http/Controllers/OpenAPI/swagger-v3.php b/app/Http/Controllers/OpenAPI/swagger-v3.php index 584bf3293a6f..39c3af2c6bb8 100644 --- a/app/Http/Controllers/OpenAPI/swagger-v3.php +++ b/app/Http/Controllers/OpenAPI/swagger-v3.php @@ -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" * ), * ), */ diff --git a/app/Http/Controllers/SelfUpdateController.php b/app/Http/Controllers/SelfUpdateController.php index abe82f44ef94..f1af705342ac 100644 --- a/app/Http/Controllers/SelfUpdateController.php +++ b/app/Http/Controllers/SelfUpdateController.php @@ -108,6 +108,8 @@ class SelfUpdateController extends BaseController $zipFile->openFile($file); $zipFile->deleteFromName(".htaccess"); + + $zipFile->rewrite(); $zipFile->extractTo(base_path()); diff --git a/app/Http/Controllers/TaskSchedulerController.php b/app/Http/Controllers/TaskSchedulerController.php index 61bb8432793c..c3f771a463e6 100644 --- a/app/Http/Controllers/TaskSchedulerController.php +++ b/app/Http/Controllers/TaskSchedulerController.php @@ -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, diff --git a/app/Http/Livewire/BillingPortalPurchase.php b/app/Http/Livewire/BillingPortalPurchase.php index 496c241b79aa..8ec53b204e60 100644 --- a/app/Http/Livewire/BillingPortalPurchase.php +++ b/app/Http/Livewire/BillingPortalPurchase.php @@ -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)); diff --git a/app/Http/Livewire/BillingPortalPurchasev2.php b/app/Http/Livewire/BillingPortalPurchasev2.php index 8ff7b6114434..b22075aaa491 100644 --- a/app/Http/Livewire/BillingPortalPurchasev2.php +++ b/app/Http/Livewire/BillingPortalPurchasev2.php @@ -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. * diff --git a/app/Http/Livewire/PaymentsTable.php b/app/Http/Livewire/PaymentsTable.php index 1ad99fa2bbcf..b6781a8917a4 100644 --- a/app/Http/Livewire/PaymentsTable.php +++ b/app/Http/Livewire/PaymentsTable.php @@ -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]) diff --git a/app/Http/Livewire/SubscriptionPlanSwitch.php b/app/Http/Livewire/SubscriptionPlanSwitch.php index 47e4ce5a0935..e5a46238ff9d 100644 --- a/app/Http/Livewire/SubscriptionPlanSwitch.php +++ b/app/Http/Livewire/SubscriptionPlanSwitch.php @@ -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), ] ); diff --git a/app/Http/Requests/Shop/StoreShopClientRequest.php b/app/Http/Requests/Shop/StoreShopClientRequest.php index 95932ea24985..e599ad0fb500 100644 --- a/app/Http/Requests/Shop/StoreShopClientRequest.php +++ b/app/Http/Requests/Shop/StoreShopClientRequest.php @@ -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'])) { diff --git a/app/Http/Requests/TaskScheduler/DestroySchedulerRequest.php b/app/Http/Requests/TaskScheduler/DestroySchedulerRequest.php index 93e15f06df95..48f5b7cb09c6 100644 --- a/app/Http/Requests/TaskScheduler/DestroySchedulerRequest.php +++ b/app/Http/Requests/TaskScheduler/DestroySchedulerRequest.php @@ -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; diff --git a/app/Http/Requests/User/StoreUserRequest.php b/app/Http/Requests/User/StoreUserRequest.php index ea5571140c3c..bf5093be71a1 100644 --- a/app/Http/Requests/User/StoreUserRequest.php +++ b/app/Http/Requests/User/StoreUserRequest.php @@ -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' => '', ]; diff --git a/app/Jobs/Bank/MatchBankTransactions.php b/app/Jobs/Bank/MatchBankTransactions.php index 12d79b9b80b5..708206c15a3f 100644 --- a/app/Jobs/Bank/MatchBankTransactions.php +++ b/app/Jobs/Bank/MatchBankTransactions.php @@ -319,7 +319,7 @@ class MatchBankTransactions implements ShouldQueue }); - }, 1); + }, 2); if(!$this->invoice) return; diff --git a/app/Jobs/Cron/SubscriptionCron.php b/app/Jobs/Cron/SubscriptionCron.php index 1242a63cbc08..beee6f82d7f2 100644 --- a/app/Jobs/Cron/SubscriptionCron.php +++ b/app/Jobs/Cron/SubscriptionCron.php @@ -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') diff --git a/app/Jobs/Invoice/InvoiceWorkflowSettings.php b/app/Jobs/Invoice/InvoiceWorkflowSettings.php index 006b8ebff76f..c9a64ac9cee4 100644 --- a/app/Jobs/Invoice/InvoiceWorkflowSettings.php +++ b/app/Jobs/Invoice/InvoiceWorkflowSettings.php @@ -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); - // }); - // } } } diff --git a/app/Jobs/Ninja/BankTransactionSync.php b/app/Jobs/Ninja/BankTransactionSync.php index 7699d951bb36..1fc0bec9d445 100644 --- a/app/Jobs/Ninja/BankTransactionSync.php +++ b/app/Jobs/Ninja/BankTransactionSync.php @@ -44,6 +44,7 @@ class BankTransactionSync implements ShouldQueue */ public function handle() { + //multiDB environment, need to foreach (MultiDB::$dbs as $db) { diff --git a/app/Jobs/Product/UpdateOrCreateProduct.php b/app/Jobs/Product/UpdateOrCreateProduct.php index a614820441d9..f5ef6ef22a50 100644 --- a/app/Jobs/Product/UpdateOrCreateProduct.php +++ b/app/Jobs/Product/UpdateOrCreateProduct.php @@ -29,6 +29,8 @@ class UpdateOrCreateProduct implements ShouldQueue public $company; + public $deleteWhenMissingModels = true; + /** * Create a new job instance. * diff --git a/app/Models/Payment.php b/app/Models/Payment.php index 153f9b551e37..159fd2c35801 100644 --- a/app/Models/Payment.php +++ b/app/Models/Payment.php @@ -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) { diff --git a/app/Models/Paymentable.php b/app/Models/Paymentable.php index 594162829132..3eaf405d2177 100644 --- a/app/Models/Paymentable.php +++ b/app/Models/Paymentable.php @@ -43,4 +43,5 @@ class Paymentable extends Pivot { return $this->belongsTo(Payment::class); } + } diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php index de04af406752..be3df720a13b 100644 --- a/app/Models/Presenters/ClientPresenter.php +++ b/app/Models/Presenters/ClientPresenter.php @@ -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'; diff --git a/app/Models/User.php b/app/Models/User.php index c018089d7818..b27f81f043f7 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -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() diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index e17e3f648518..1568a0658021 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -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) diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index ad099a102351..28664dd3b9aa 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -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() - { - } - } diff --git a/app/Repositories/ClientRepository.php b/app/Repositories/ClientRepository.php index 6114916e80ad..9d5a0c757d78 100644 --- a/app/Repositories/ClientRepository.php +++ b/app/Repositories/ClientRepository.php @@ -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 { diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 3fb7b920f0ce..3a12be819285 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -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; diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php index e002ad1288d7..775a3dbd29f9 100644 --- a/app/Services/ClientPortal/InstantPayment.php +++ b/app/Services/ClientPortal/InstantPayment.php @@ -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); diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index a5a769ba2f5d..d14dbdd91228 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -51,7 +51,7 @@ class DeletePayment } - }, 1); + }, 2); return $this->payment; diff --git a/app/Services/Payment/SendEmail.php b/app/Services/Payment/SendEmail.php index d9f6907a2610..828023381385 100644 --- a/app/Services/Payment/SendEmail.php +++ b/app/Services/Payment/SendEmail.php @@ -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)); } } diff --git a/app/Services/Scheduler/SchedulerService.php b/app/Services/Scheduler/SchedulerService.php index 40de48ef3e6c..88319d784b91 100644 --- a/app/Services/Scheduler/SchedulerService.php +++ b/app/Services/Scheduler/SchedulerService.php @@ -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; diff --git a/app/Services/Subscription/SubscriptionService.php b/app/Services/Subscription/SubscriptionService.php index 3af24a43ea7a..3c59d33fdfbc 100644 --- a/app/Services/Subscription/SubscriptionService.php +++ b/app/Services/Subscription/SubscriptionService.php @@ -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, diff --git a/app/Utils/Traits/SubscriptionHooker.php b/app/Utils/Traits/SubscriptionHooker.php index 52b05fbb1e48..8dfaba0171fc 100644 --- a/app/Utils/Traits/SubscriptionHooker.php +++ b/app/Utils/Traits/SubscriptionHooker.php @@ -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']); } diff --git a/config/ninja.php b/config/ninja.php index 0b3a99c33a3c..9006c72572b6 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -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', ''), diff --git a/database/factories/SchedulerFactory.php b/database/factories/SchedulerFactory.php index e60a53211da3..8b839c5e6b2e 100644 --- a/database/factories/SchedulerFactory.php +++ b/database/factories/SchedulerFactory.php @@ -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', ]; } } diff --git a/database/schema/db-ninja-01-schema.dump b/database/schema/mysql-schema.dump similarity index 98% rename from database/schema/db-ninja-01-schema.dump rename to database/schema/mysql-schema.dump index 9de6c462deb2..bdf30e6abb5e 100644 --- a/database/schema/db-ninja-01-schema.dump +++ b/database/schema/mysql-schema.dump @@ -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); diff --git a/phpunit.yml b/phpunit.yml deleted file mode 100644 index d77a3ccffa97..000000000000 --- a/phpunit.yml +++ /dev/null @@ -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 diff --git a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php index 6c0ff64b36c7..b5398e952937 100644 --- a/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php +++ b/resources/views/portal/ninja2020/components/livewire/billing-portal-purchasev2.blade.php @@ -51,18 +51,34 @@
-Out of stock
+ @else{{ ctrans('texts.qty') }}
+ @endif +