1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-21 00:41:34 +02:00

Merge pull request #8173 from turbo124/v5-develop

Add company logo size to company settings object
This commit is contained in:
David Bomba 2023-01-19 14:46:26 +11:00 committed by GitHub
commit 2aea9eade3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 1218 additions and 685 deletions

View File

@ -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 }}

View File

@ -1 +1 @@
5.5.56
5.5.57

View File

@ -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,

View File

@ -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');

View File

@ -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();

View File

@ -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',

View File

@ -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;

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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');

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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.'%');
});
}

View File

@ -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);
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -11,7 +11,6 @@
namespace App\Filters;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
/**

View File

@ -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]);
}
/**

View File

@ -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;
}

View File

@ -12,7 +12,6 @@
namespace App\Filters;
use App\Models\Quote;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
/**

View File

@ -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);

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -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"
* )
* )
*/

View File

@ -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",

View File

@ -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);
}

View File

@ -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"
* ),
* ),
*/

View File

@ -108,6 +108,8 @@ class SelfUpdateController extends BaseController
$zipFile->openFile($file);
$zipFile->deleteFromName(".htaccess");
$zipFile->rewrite();
$zipFile->extractTo(base_path());

View File

@ -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,

View File

@ -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));

View File

@ -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.
*

View File

@ -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])

View File

@ -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), ]
);

View File

@ -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'])) {

View File

@ -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;

View File

@ -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' => '',
];

View File

@ -319,7 +319,7 @@ class MatchBankTransactions implements ShouldQueue
});
}, 1);
}, 2);
if(!$this->invoice)
return;

View File

@ -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')

View File

@ -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);
// });
// }
}
}

View File

@ -44,6 +44,7 @@ class BankTransactionSync implements ShouldQueue
*/
public function handle()
{
//multiDB environment, need to
foreach (MultiDB::$dbs as $db)
{

View File

@ -29,6 +29,8 @@ class UpdateOrCreateProduct implements ShouldQueue
public $company;
public $deleteWhenMissingModels = true;
/**
* Create a new job instance.
*

View File

@ -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) {

View File

@ -43,4 +43,5 @@ class Paymentable extends Pivot
{
return $this->belongsTo(Payment::class);
}
}

View File

@ -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';

View File

@ -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()

View File

@ -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)

View File

@ -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()
{
}
}

View File

@ -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
{

View File

@ -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;

View File

@ -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);

View File

@ -51,7 +51,7 @@ class DeletePayment
}
}, 1);
}, 2);
return $this->payment;

View File

@ -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));
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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']);
}

View File

@ -14,8 +14,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => '5.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', ''),

View File

@ -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',
];
}
}

View File

@ -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);

View File

@ -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

View File

@ -51,18 +51,34 @@
</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>
@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>
<select wire:model.debounce.300ms="data.{{ $index }}.recurring_qty" class="rounded-md border-gray-300 shadow-sm sm:text-sm">
<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
</select>
}
@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
</div>

View File

@ -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) }}

View File

@ -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

View File

@ -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());
}
}

View File

@ -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([

View File

@ -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);

View File

@ -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) {

View 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());
}
}

View File

@ -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());
}
}

View File

@ -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 = [

View File

@ -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();
}

View File

@ -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');

View File

@ -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')),
]);

View File

@ -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()
{

View File

@ -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([

View File

@ -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);

View File

@ -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([

View File

@ -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([

View File

@ -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 = [

View File

@ -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()

View File

@ -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([

View File

@ -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 = [];

View File

@ -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]);

View File

@ -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'));
}

View File

@ -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([

View File

@ -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 = [

View File

@ -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 = [

View File

@ -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 = [

View File

@ -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