mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 13:54:30 -04:00
Merge branch 'v5-develop' into translations
This commit is contained in:
commit
460d59b7d2
@ -1 +1 @@
|
||||
5.5.42
|
||||
5.5.43
|
@ -22,6 +22,8 @@ use App\Jobs\Company\CreateCompanyTaskStatuses;
|
||||
use App\Jobs\Ninja\CompanySizeCheck;
|
||||
use App\Jobs\Util\VersionCheck;
|
||||
use App\Models\Account;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
@ -39,6 +41,7 @@ use App\Models\Vendor;
|
||||
use App\Models\VendorContact;
|
||||
use App\Repositories\InvoiceRepository;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\AppSetup;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Carbon\Carbon;
|
||||
@ -51,7 +54,7 @@ use Illuminate\Support\Str;
|
||||
|
||||
class DemoMode extends Command
|
||||
{
|
||||
use MakesHash, GeneratesCounter;
|
||||
use MakesHash, GeneratesCounter, AppSetup;
|
||||
|
||||
protected $signature = 'ninja:demo-mode';
|
||||
|
||||
@ -81,34 +84,14 @@ class DemoMode extends Command
|
||||
|
||||
$cached_tables = config('ninja.cached_tables');
|
||||
|
||||
foreach ($cached_tables as $name => $class) {
|
||||
if (! Cache::has($name)) {
|
||||
// check that the table exists in case the migration is pending
|
||||
if (! Schema::hasTable((new $class())->getTable())) {
|
||||
continue;
|
||||
}
|
||||
if ($name == 'payment_terms') {
|
||||
$orderBy = 'num_days';
|
||||
} elseif ($name == 'fonts') {
|
||||
$orderBy = 'sort_order';
|
||||
} elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) {
|
||||
$orderBy = 'name';
|
||||
} else {
|
||||
$orderBy = 'id';
|
||||
}
|
||||
$tableData = $class::orderBy($orderBy)->get();
|
||||
if ($tableData->count()) {
|
||||
Cache::forever($name, $tableData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Migrating');
|
||||
Artisan::call('migrate:fresh --force');
|
||||
|
||||
$this->info('Seeding');
|
||||
Artisan::call('db:seed --force');
|
||||
|
||||
$this->buildCache(true);
|
||||
|
||||
$this->info('Seeding Random Data');
|
||||
$this->createSmallAccount();
|
||||
|
||||
@ -223,6 +206,18 @@ class DemoMode extends Command
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
$bi = BankIntegration::factory()->create([
|
||||
'account_id' => $account->id,
|
||||
'company_id' => $company->id,
|
||||
'user_id' => $user->id,
|
||||
]);
|
||||
|
||||
BankTransaction::factory()->count(50)->create([
|
||||
'bank_integration_id' => $bi->id,
|
||||
'user_id' => $user->id,
|
||||
'company_id' => $company->id,
|
||||
]);
|
||||
|
||||
$this->info('Creating '.$this->count.' clients');
|
||||
|
||||
for ($x = 0; $x < $this->count; $x++) {
|
||||
|
@ -42,7 +42,7 @@ class S3Cleanup extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! Ninja::isHosted()) {
|
||||
if (!Ninja::isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,7 @@ use App\Utils\Traits\MakesReminders;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\App;
|
||||
|
||||
//@deprecated 27-11-2022 - only ever should be used for testing
|
||||
class SendRemindersCron extends Command
|
||||
{
|
||||
use MakesReminders, MakesDates;
|
||||
|
@ -48,46 +48,46 @@ class Kernel extends ConsoleKernel
|
||||
$schedule->job(new VersionCheck)->daily();
|
||||
|
||||
/* Checks and cleans redundant files */
|
||||
$schedule->job(new DiskCleanup)->daily()->withoutOverlapping();
|
||||
$schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping()->name('disk-cleanup-job')->onOneServer();
|
||||
|
||||
/* Send reminders */
|
||||
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping();
|
||||
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping()->name('reminder-job')->onOneServer();
|
||||
|
||||
/* Returns the number of jobs in the queue */
|
||||
$schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping();
|
||||
$schedule->job(new QueueSize)->everyFiveMinutes()->withoutOverlapping()->name('queue-size-job')->onOneServer();
|
||||
|
||||
/* Checks for large companies and marked them as is_large */
|
||||
$schedule->job(new CompanySizeCheck)->dailyAt('23:20')->withoutOverlapping();
|
||||
$schedule->job(new CompanySizeCheck)->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
|
||||
|
||||
/* Pulls in the latest exchange rates */
|
||||
$schedule->job(new UpdateExchangeRates)->dailyAt('23:30')->withoutOverlapping();
|
||||
$schedule->job(new UpdateExchangeRates)->dailyAt('23:30')->withoutOverlapping()->name('exchange-rate-job')->onOneServer();
|
||||
|
||||
/* Runs cleanup code for subscriptions */
|
||||
$schedule->job(new SubscriptionCron)->daily()->withoutOverlapping();
|
||||
$schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping()->name('subscription-job')->onOneServer();
|
||||
|
||||
/* Sends recurring invoices*/
|
||||
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
|
||||
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer();
|
||||
|
||||
/* Sends recurring invoices*/
|
||||
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping();
|
||||
$schedule->job(new RecurringExpensesCron)->dailyAt('00:10')->withoutOverlapping()->name('recurring-expense-job')->onOneServer();
|
||||
|
||||
/* Fires notifications for expired Quotes */
|
||||
$schedule->job(new QuoteCheckExpired)->dailyAt('05:00')->withoutOverlapping();
|
||||
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping()->name('quote-expired-job')->onOneServer();
|
||||
|
||||
/* Performs auto billing */
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:00')->withoutOverlapping();
|
||||
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping()->name('auto-bill-job')->onOneServer();
|
||||
|
||||
/* Checks the status of the scheduler */
|
||||
$schedule->job(new SchedulerCheck)->daily()->withoutOverlapping();
|
||||
$schedule->job(new SchedulerCheck)->dailyAt('01:10')->withoutOverlapping();
|
||||
|
||||
/* Checks for scheduled tasks */
|
||||
$schedule->job(new TaskScheduler())->daily()->withoutOverlapping();
|
||||
$schedule->job(new TaskScheduler())->dailyAt('06:50')->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
||||
|
||||
/* Performs system maintenance such as pruning the backup table */
|
||||
$schedule->job(new SystemMaintenance)->weekly()->withoutOverlapping();
|
||||
$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:00')->withoutOverlapping();
|
||||
$schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
|
||||
@ -105,11 +105,11 @@ class Kernel extends ConsoleKernel
|
||||
//not used @deprecate
|
||||
// $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->daily('02:00')->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-01')->dailyAt('02:10')->withoutOverlapping()->name('check-data-db-1-job')->onOneServer();
|
||||
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:05')->withoutOverlapping();
|
||||
$schedule->command('ninja:check-data --database=db-ninja-02')->dailyAt('02:20')->withoutOverlapping()->name('check-data-db-2-job')->onOneServer();
|
||||
|
||||
$schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping();
|
||||
$schedule->command('ninja:s3-cleanup')->dailyAt('23:15')->withoutOverlapping()->name('s3-cleanup-job')->onOneServer();
|
||||
|
||||
}
|
||||
|
||||
|
@ -182,7 +182,7 @@ class Handler extends ExceptionHandler
|
||||
} elseif ($exception instanceof FatalThrowableError && $request->expectsJson()) {
|
||||
return response()->json(['message'=>'Fatal error'], 500);
|
||||
} elseif ($exception instanceof AuthorizationException) {
|
||||
return response()->json(['message'=>'You are not authorized to view or perform this action'], 401);
|
||||
return response()->json(['message'=> $exception->getMessage()], 401);
|
||||
} elseif ($exception instanceof TokenMismatchException) {
|
||||
return redirect()
|
||||
->back()
|
||||
|
30
app/Factory/BankTransactionRuleFactory.php
Normal file
30
app/Factory/BankTransactionRuleFactory.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\BankTransactionRule;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BankTransactionRuleFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id) :BankTransactionRule
|
||||
{
|
||||
$bank_transaction_rule = new BankTransactionRule;
|
||||
$bank_transaction_rule->user_id = $user_id;
|
||||
$bank_transaction_rule->company_id = $company_id;
|
||||
|
||||
$bank_transaction_rule->name = '';
|
||||
$bank_transaction_rule->rules = [];
|
||||
|
||||
return $bank_transaction_rule;
|
||||
}
|
||||
}
|
133
app/Filters/BankTransactionRuleFilters.php
Normal file
133
app/Filters/BankTransactionRuleFilters.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* BankTransactionRuleilters.
|
||||
*/
|
||||
class BankTransactionRuleFilters extends QueryFilters
|
||||
{
|
||||
/**
|
||||
* Filter by name.
|
||||
*
|
||||
* @param string $name
|
||||
* @return Builder
|
||||
*/
|
||||
public function name(string $name = ''): Builder
|
||||
{
|
||||
if(strlen($name) >=1)
|
||||
return $this->builder->where('name', 'like', '%'.$name.'%');
|
||||
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
* @param string query filter
|
||||
* @return Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function filter(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('bank_transaction_rules.name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$table = 'bank_transaction_rules';
|
||||
$filters = explode(',', $filter);
|
||||
|
||||
return $this->builder->where(function ($query) use ($filters, $table) {
|
||||
$query->whereNull($table.'.id');
|
||||
|
||||
if (in_array(parent::STATUS_ACTIVE, $filters)) {
|
||||
$query->orWhereNull($table.'.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.
|
||||
*
|
||||
* @param string sort formatted as column|asc
|
||||
* @return Builder
|
||||
*/
|
||||
public function sort(string $sort) : Builder
|
||||
{
|
||||
$sort_col = explode('|', $sort);
|
||||
|
||||
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.
|
||||
*
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function entityFilter()
|
||||
{
|
||||
//return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
return $this->builder->company();
|
||||
}
|
||||
}
|
@ -185,7 +185,7 @@ class InvoiceFilters 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);
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
/**
|
||||
@ -19,6 +20,8 @@ use Illuminate\Database\Eloquent\Builder;
|
||||
*/
|
||||
class TaskFilters extends QueryFilters
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Filter based on search text.
|
||||
*
|
||||
@ -111,6 +114,16 @@ class TaskFilters extends QueryFilters
|
||||
});
|
||||
}
|
||||
|
||||
public function project_tasks($project)
|
||||
{
|
||||
|
||||
if (strlen($project) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where('project_id', $this->decodePrimaryKey($project));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort.
|
||||
*
|
||||
|
@ -76,7 +76,7 @@ class EpcQrGenerator
|
||||
$this->formatMoney($this->amount),
|
||||
$this->sepa['purpose'],
|
||||
substr($this->invoice->number,0,34),
|
||||
substr($this->invoice->public_notes,0,139),
|
||||
'',
|
||||
''
|
||||
)), "\n");
|
||||
|
||||
|
@ -110,6 +110,8 @@ class ActivityController extends BaseController
|
||||
'vendor' => $activity->vendor ? $activity->vendor : '',
|
||||
'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '',
|
||||
'purchase_order' => $activity->purchase_order ? $activity->purchase_order : '',
|
||||
'subscription' => $activity->subscription ? $activity->subscription : '',
|
||||
'vendor_contact' => $activity->vendor_contact ? $activity->vendor_contact : '',
|
||||
];
|
||||
|
||||
return array_merge($arr, $activity->toArray());
|
||||
|
@ -481,19 +481,32 @@ class BankTransactionController extends BaseController
|
||||
{
|
||||
$action = request()->input('action');
|
||||
|
||||
if(!in_array($action, ['archive', 'restore', 'delete']))
|
||||
if(!in_array($action, ['archive', 'restore', 'delete', 'convert_matched']))
|
||||
return response()->json(['message' => 'Unsupported action.'], 400);
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$bank_transactions = BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
|
||||
|
||||
$bank_transactions->each(function ($bank_transaction, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $bank_transaction)) {
|
||||
$this->bank_transaction_repo->{$action}($bank_transaction);
|
||||
if($action == 'convert_matched') //catch this action
|
||||
{
|
||||
if(auth()->user()->isAdmin())
|
||||
{
|
||||
$this->bank_transaction_repo->convert_matched($bank_transactions);
|
||||
}
|
||||
});
|
||||
else
|
||||
return;
|
||||
}
|
||||
else {
|
||||
|
||||
$bank_transactions->each(function ($bank_transaction, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $bank_transaction)) {
|
||||
$this->bank_transaction_repo->{$action}($bank_transaction);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
||||
|
||||
return $this->listResponse(BankTransaction::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
|
505
app/Http/Controllers/BankTransactionRuleController.php
Normal file
505
app/Http/Controllers/BankTransactionRuleController.php
Normal file
@ -0,0 +1,505 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\BankTransactionFactory;
|
||||
use App\Factory\BankTransactionRuleFactory;
|
||||
use App\Filters\BankTransactionFilters;
|
||||
use App\Filters\BankTransactionRuleFilters;
|
||||
use App\Helpers\Bank\Yodlee\Yodlee;
|
||||
use App\Http\Requests\BankTransactionRule\CreateBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransactionRule\DestroyBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransactionRule\EditBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransactionRule\ShowBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransactionRule\StoreBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransactionRule\UpdateBankTransactionRuleRequest;
|
||||
use App\Http\Requests\BankTransaction\AdminBankTransactionRuleRequest;
|
||||
use App\Http\Requests\Import\PreImportRequest;
|
||||
use App\Jobs\Bank\MatchBankTransactionRules;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Repositories\BankTransactionRepository;
|
||||
use App\Repositories\BankTransactionRuleRepository;
|
||||
use App\Services\Bank\BankMatchingService;
|
||||
use App\Transformers\BankTransactionRuleTransformer;
|
||||
use App\Transformers\BankTransactionTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class BankTransactionRuleController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = BankTransactionRule::class;
|
||||
|
||||
protected $entity_transformer = BankTransactionRuleTransformer::class;
|
||||
|
||||
protected $bank_transaction_repo;
|
||||
|
||||
public function __construct(BankTransactionRuleRepository $bank_transaction_repo)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->bank_transaction_repo = $bank_transaction_repo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bank_transaction_rules",
|
||||
* operationId="getBankTransactionRules",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Gets a list of bank_transaction_rules",
|
||||
* description="Lists all bank transaction rules",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\Parameter(
|
||||
* name="rows",
|
||||
* in="query",
|
||||
* description="The number of bank integrations to return",
|
||||
* example="50",
|
||||
* required=false,
|
||||
* @OA\Schema(
|
||||
* type="number",
|
||||
* format="integer",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A list of bank integrations",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
* @param BankTransactionFilters $filter
|
||||
* @return Response|mixed
|
||||
*/
|
||||
public function index(BankTransactionRuleFilters $filters)
|
||||
{
|
||||
|
||||
$bank_transaction_rules = BankTransactionRule::filter($filters);
|
||||
|
||||
return $this->listResponse($bank_transaction_rules);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param ShowBankTransactionRuleRequest $request
|
||||
* @param BankTransactionRule $bank_transaction_rule
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bank_transaction_rules/{id}",
|
||||
* operationId="showBankTransactionRule",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Shows a bank_transaction",
|
||||
* description="Displays a bank_transaction by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Bank Transaction RuleHashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the bank_transaction rule object",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function show(ShowBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
return $this->itemResponse($bank_transaction_rule);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param EditBankTransactionRuleRequest $request
|
||||
* @param BankTransactionRule $bank_transaction_rule
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bank_transaction_rules/{id}/edit",
|
||||
* operationId="editBankTransactionRule",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Shows a bank_transaction for editing",
|
||||
* description="Displays a bank_transaction by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Bank Transaction Rule Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the bank_transaction rule object",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function edit(EditBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
return $this->itemResponse($bank_transaction_rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param UpdateBankTransactionRuleRequest $request
|
||||
* @param BankTransactionRule $bank_transaction_rule
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/bank_transaction_rules/{id}",
|
||||
* operationId="updateBankTransactionRule",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Updates a bank_transaction Rule",
|
||||
* description="Handles the updating of a bank_transaction rule by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Bank Transaction Rule Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the bank_transaction rule object",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function update(UpdateBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
|
||||
//stubs for updating the model
|
||||
$bank_transaction = $this->bank_transaction_repo->save($request->all(), $bank_transaction_rule);
|
||||
|
||||
return $this->itemResponse($bank_transaction_rule->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param CreateBankTransactionRuleRequest $request
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/bank_transaction_rules/create",
|
||||
* operationId="getBankTransactionRulesCreate",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Gets a new blank bank_transaction rule object",
|
||||
* description="Returns a blank object with default values",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A blank bank_transaction rule object",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function create(CreateBankTransactionRuleRequest $request)
|
||||
{
|
||||
$bank_transaction_rule = BankTransactionRuleFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id);
|
||||
|
||||
return $this->itemResponse($bank_transaction_rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param StoreBankTransactionRuleRequest $request
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/bank_transaction_rules",
|
||||
* operationId="storeBankTransaction",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Adds a bank_transaction rule",
|
||||
* description="Adds an bank_transaction to a company",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved bank_transaction rule object",
|
||||
* @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/BankTransactionRule"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function store(StoreBankTransactionRuleRequest $request)
|
||||
{
|
||||
//stub to store the model
|
||||
$bank_transaction_rule = $this->bank_transaction_repo->save($request->all(), BankTransactionRuleFactory::create(auth()->user()->company()->id, auth()->user()->id, auth()->user()->account_id));
|
||||
|
||||
return $this->itemResponse($bank_transaction_rule);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param DestroyBankTransactionRuleRequest $request
|
||||
* @param BankTransactionRule $bank_transaction_rule
|
||||
* @return Response
|
||||
*
|
||||
*
|
||||
* @throws \Exception
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/bank_transaction_rules/{id}",
|
||||
* operationId="deleteBankTransactionRule",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Deletes a bank_transaction rule",
|
||||
* description="Handles the deletion of a bank_transaction rule by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Bank Transaction Rule Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a HTTP status",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function destroy(DestroyBankTransactionRuleRequest $request, BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
$this->bank_transaction_repo->delete($bank_transaction_rule);
|
||||
|
||||
return $this->itemResponse($bank_transaction_rule->fresh());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform bulk actions on the list view.
|
||||
*
|
||||
* @return Collection
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/bank_transation_rules/bulk",
|
||||
* operationId="bulkBankTransactionRules",
|
||||
* tags={"bank_transaction_rules"},
|
||||
* summary="Performs bulk actions on an array of bank_transation rules",
|
||||
* description="",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\RequestBody(
|
||||
* description="Action paramters",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* description="Array of hashed IDs to be bulk 'actioned",
|
||||
* example="[0,1,2,3]",
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Bulk Action response",
|
||||
* @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
$action = request()->input('action');
|
||||
|
||||
if(!in_array($action, ['archive', 'restore', 'delete']))
|
||||
return response()->json(['message' => 'Unsupported action.'], 400);
|
||||
|
||||
$ids = request()->input('ids');
|
||||
|
||||
$bank_transaction_rules = BankTransactionRule::withTrashed()->whereIn('id', $this->transformKeys($ids))->company()->get();
|
||||
|
||||
$bank_transaction_rules->each(function ($bank_transaction_rule, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $bank_transaction_rule)) {
|
||||
$this->bank_transaction_repo->{$action}($bank_transaction_rule);
|
||||
}
|
||||
});
|
||||
|
||||
/* Need to understand which permission are required for the given bulk action ie. view / edit */
|
||||
|
||||
return $this->listResponse(BankTransactionRule::withTrashed()->whereIn('id', $this->transformKeys($ids))->company());
|
||||
}
|
||||
|
||||
}
|
@ -108,6 +108,7 @@ class BaseController extends Controller
|
||||
'company.system_logs',
|
||||
'company.bank_integrations',
|
||||
'company.bank_transactions',
|
||||
'company.bank_transaction_rules',
|
||||
];
|
||||
|
||||
private $mini_load = [
|
||||
@ -126,6 +127,7 @@ class BaseController extends Controller
|
||||
'company.expense_categories',
|
||||
'company.subscriptions',
|
||||
'company.bank_integrations',
|
||||
'company.bank_transaction_rules',
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
@ -456,6 +458,13 @@ class BaseController extends Controller
|
||||
$query->where('bank_transactions.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
'company.bank_transaction_rules'=> function ($query) use ($updated_at, $user) {
|
||||
$query->where('updated_at', '>=', $updated_at);
|
||||
|
||||
if (! $user->isAdmin()) {
|
||||
$query->where('bank_transaction_rules.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
@ -530,6 +539,12 @@ class BaseController extends Controller
|
||||
$query->where('bank_integrations.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
'company.bank_transaction_rules'=> function ($query) use ($user) {
|
||||
|
||||
if (! $user->isAdmin()) {
|
||||
$query->where('bank_transaction_rules.user_id', $user->id);
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -41,6 +41,25 @@ class SubscriptionPurchaseController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function upgrade(Subscription $subscription, Request $request)
|
||||
{
|
||||
/* Make sure the contact is logged into the correct company for this subscription */
|
||||
if (auth()->guard('contact')->user() && auth()->guard('contact')->user()->company_id != $subscription->company_id) {
|
||||
auth()->guard('contact')->logout();
|
||||
$request->session()->invalidate();
|
||||
}
|
||||
|
||||
if ($request->has('locale')) {
|
||||
$this->setLocale($request->query('locale'));
|
||||
}
|
||||
|
||||
return view('billing-portal.purchasev2', [
|
||||
'subscription' => $subscription,
|
||||
'hash' => Str::uuid()->toString(),
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set locale for incoming request.
|
||||
*
|
||||
@ -56,4 +75,7 @@ class SubscriptionPurchaseController extends Controller
|
||||
App::setLocale($record->locale);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ use App\Http\Requests\Invoice\StoreInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\UpdateInvoiceRequest;
|
||||
use App\Http\Requests\Invoice\UpdateReminderRequest;
|
||||
use App\Http\Requests\Invoice\UploadInvoiceRequest;
|
||||
use App\Jobs\Cron\AutoBill;
|
||||
use App\Jobs\Entity\EmailEntity;
|
||||
use App\Jobs\Invoice\BulkInvoiceJob;
|
||||
use App\Jobs\Invoice\StoreInvoice;
|
||||
@ -427,13 +428,13 @@ class InvoiceController extends BaseController
|
||||
|
||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
$transaction = [
|
||||
'invoice' => $invoice->transaction_event(),
|
||||
'payment' => [],
|
||||
'client' => $invoice->client->transaction_event(),
|
||||
'credit' => [],
|
||||
'metadata' => [],
|
||||
];
|
||||
// $transaction = [
|
||||
// 'invoice' => $invoice->transaction_event(),
|
||||
// 'payment' => [],
|
||||
// 'client' => $invoice->client->transaction_event(),
|
||||
// 'credit' => [],
|
||||
// 'metadata' => [],
|
||||
// ];
|
||||
|
||||
// TransactionLog::dispatch(TransactionEvent::INVOICE_UPDATED, $transaction, $invoice->company->db);
|
||||
|
||||
@ -696,11 +697,14 @@ class InvoiceController extends BaseController
|
||||
{
|
||||
/*If we are using bulk actions, we don't want to return anything */
|
||||
switch ($action) {
|
||||
case 'auto_bill':
|
||||
AutoBill::dispatch($invoice->id, $invoice->company->db);
|
||||
return $this->itemResponse($invoice);
|
||||
|
||||
case 'clone_to_invoice':
|
||||
$invoice = CloneInvoiceFactory::create($invoice, auth()->user()->id);
|
||||
|
||||
return $this->itemResponse($invoice);
|
||||
break;
|
||||
|
||||
case 'clone_to_quote':
|
||||
$quote = CloneInvoiceToQuoteFactory::create($invoice, auth()->user()->id);
|
||||
|
||||
@ -767,7 +771,7 @@ class InvoiceController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'cancel':
|
||||
$invoice = $invoice->service()->handleCancellation()->deletePdf()->touchPdf()->save();
|
||||
$invoice = $invoice->service()->handleCancellation()->touchPdf()->save();
|
||||
|
||||
if (! $bulk) {
|
||||
$this->itemResponse($invoice);
|
||||
@ -777,7 +781,7 @@ class InvoiceController extends BaseController
|
||||
case 'email':
|
||||
//check query parameter for email_type and set the template else use calculateTemplate
|
||||
|
||||
if (request()->has('email_type') && property_exists($invoice->company->settings, request()->input('email_type'))) {
|
||||
if (request()->has('email_type') && in_array(request()->input('email_type'), ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'custom1', 'custom2', 'custom3'])) {
|
||||
$this->reminder_template = $invoice->client->getSetting(request()->input('email_type'));
|
||||
} else {
|
||||
$this->reminder_template = $invoice->calculateTemplate('invoice');
|
||||
|
@ -181,7 +181,10 @@ class MigrationController extends BaseController
|
||||
$company->tasks()->forceDelete();
|
||||
$company->vendors()->forceDelete();
|
||||
$company->expenses()->forceDelete();
|
||||
$company->purchase_orders()->forceDelete();
|
||||
$company->bank_transaction_rules()->forceDelete();
|
||||
$company->bank_transactions()->forceDelete();
|
||||
$company->bank_integrations()->forceDelete();
|
||||
|
||||
$company->all_activities()->forceDelete();
|
||||
|
||||
$settings = $company->settings;
|
||||
|
10
app/Http/Controllers/OpenAPI/BTRulesSchema.php
Normal file
10
app/Http/Controllers/OpenAPI/BTRulesSchema.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="BTRules",
|
||||
* type="object",
|
||||
* @OA\Property(property="data_key", type="string", example="description,amount", description="The key to search"),
|
||||
* @OA\Property(property="operator", type="string", example=">", description="The operator flag of the search"),
|
||||
* @OA\Property(property="value", type="string" ,example="bob", description="The value to search for"),
|
||||
* )
|
||||
*/
|
@ -6,7 +6,7 @@
|
||||
* @OA\Property(property="id", type="string", example="AS3df3A", description="The bank integration hashed id"),
|
||||
* @OA\Property(property="company_id", type="string", example="AS3df3A", description="The company hashed id"),
|
||||
* @OA\Property(property="user_id", type="string", example="AS3df3A", description="The user hashed id"),
|
||||
* @OA\Property(property="transaction_id", type="integer", example=343434, description="The id of the transaction"),
|
||||
* @OA\Property(property="transaction_id", type="integer", example=343434, description="The id of the transaction rule"),
|
||||
* @OA\Property(property="amount", type="number", example=10.00, description="The transaction amount"),
|
||||
* @OA\Property(property="currency_id", type="string", example="1", description="The currency ID of the currency"),
|
||||
* @OA\Property(property="account_type", type="string", example="creditCard", description="The account type"),
|
||||
|
25
app/Http/Controllers/OpenAPI/BankTransactionRule.php
Normal file
25
app/Http/Controllers/OpenAPI/BankTransactionRule.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="BankTransactionRule",
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="string", example="AS3df3A", description="The bank transaction rules hashed id"),
|
||||
* @OA\Property(property="company_id", type="string", example="AS3df3A", description="The company hashed id"),
|
||||
* @OA\Property(property="user_id", type="string", example="AS3df3A", description="The user hashed id"),
|
||||
* @OA\Property(property="name", type="string", example="Rule 1", description="The name of the transaction"),
|
||||
* @OA\Property(
|
||||
* property="rules",
|
||||
* type="array",
|
||||
* description="A mapped collection of the sub rules for the BankTransactionRule",
|
||||
* @OA\Items(
|
||||
* ref="#/components/schemas/BTRules",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Property(property="auto_convert", type="boolean", example=true, description="Flags whether the rule converts the transaction automatically"),
|
||||
* @OA\Property(property="matches_on_all", type="boolean", example=true, description="Flags whether all subrules are required for the match"),
|
||||
* @OA\Property(property="applies_to", type="string", example="CREDIT", description="Flags whether the rule applies to a CREDIT or DEBIT"),
|
||||
* @OA\Property(property="client_id", type="string", example="AS3df3A", description="The client hashed id"),
|
||||
* @OA\Property(property="vendor_id", type="string", example="AS3df3A", description="The vendor hashed id"),
|
||||
* @OA\Property(property="category_id", type="string", example="AS3df3A", description="The category hashed id"),
|
||||
* )
|
||||
*/
|
528
app/Http/Livewire/BillingPortalPurchasev2.php
Normal file
528
app/Http/Livewire/BillingPortalPurchasev2.php
Normal file
@ -0,0 +1,528 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Livewire;
|
||||
|
||||
use App\Factory\ClientFactory;
|
||||
use App\Jobs\Mail\NinjaMailerJob;
|
||||
use App\Jobs\Mail\NinjaMailerObject;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Mail\ContactPasswordlessLogin;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Subscription;
|
||||
use App\Repositories\ClientContactRepository;
|
||||
use App\Repositories\ClientRepository;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use App\DataMapper\ClientSettings;
|
||||
use Livewire\Component;
|
||||
|
||||
class BillingPortalPurchasev2 extends Component
|
||||
{
|
||||
/**
|
||||
* Random hash generated by backend to handle the tracking of state.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $hash;
|
||||
|
||||
/**
|
||||
* Top level text on the left side of billing page.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $heading_text;
|
||||
|
||||
|
||||
/**
|
||||
* E-mail address model for user input.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $email;
|
||||
|
||||
/**
|
||||
* Password model for user input.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $password;
|
||||
|
||||
/**
|
||||
* Instance of subscription.
|
||||
*
|
||||
* @var Subscription
|
||||
*/
|
||||
public $subscription;
|
||||
|
||||
/**
|
||||
* Instance of client contact.
|
||||
*
|
||||
* @var null|ClientContact
|
||||
*/
|
||||
public $contact;
|
||||
|
||||
/**
|
||||
* Rules for validating the form.
|
||||
*
|
||||
* @var \string[][]
|
||||
*/
|
||||
protected $rules = [
|
||||
'email' => ['required', 'email'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Id for CompanyGateway record.
|
||||
*
|
||||
* @var string|integer
|
||||
*/
|
||||
public $company_gateway_id;
|
||||
|
||||
/**
|
||||
* Id for GatewayType.
|
||||
*
|
||||
* @var string|integer
|
||||
*/
|
||||
public $payment_method_id;
|
||||
|
||||
private $user_coupon;
|
||||
|
||||
/**
|
||||
* List of steps that frontend form follows.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $steps = [
|
||||
'passed_email' => false,
|
||||
'existing_user' => false,
|
||||
'fetched_payment_methods' => false,
|
||||
'fetched_client' => false,
|
||||
'show_start_trial' => false,
|
||||
'passwordless_login_sent' => false,
|
||||
'started_payment' => false,
|
||||
'discount_applied' => false,
|
||||
'show_loading_bar' => false,
|
||||
'not_eligible' => null,
|
||||
'not_eligible_message' => null,
|
||||
'payment_required' => true,
|
||||
];
|
||||
|
||||
/**
|
||||
* List of payment methods fetched from client.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $methods = [];
|
||||
|
||||
/**
|
||||
* Instance of \App\Models\Invoice
|
||||
*
|
||||
* @var Invoice
|
||||
*/
|
||||
public $invoice;
|
||||
|
||||
/**
|
||||
* Coupon model for user input
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $coupon;
|
||||
|
||||
/**
|
||||
* Quantity for seats
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $quantity;
|
||||
|
||||
/**
|
||||
* First-hit request data (queries, locales...).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $request_data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $price;
|
||||
|
||||
/**
|
||||
* Disabled state of passwordless login button.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $passwordless_login_btn = false;
|
||||
|
||||
/**
|
||||
* Instance of company.
|
||||
*
|
||||
* @var Company
|
||||
*/
|
||||
public $company;
|
||||
|
||||
/**
|
||||
* Campaign reference.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $campaign;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->company->db);
|
||||
|
||||
$this->quantity = 1;
|
||||
|
||||
$this->price = $this->subscription->price;
|
||||
|
||||
if (request()->query('coupon')) {
|
||||
$this->coupon = request()->query('coupon');
|
||||
$this->handleCoupon();
|
||||
}
|
||||
elseif(strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Laracasts\Presenter\Exceptions\PresenterException
|
||||
*/
|
||||
protected function createBlankClient()
|
||||
{
|
||||
$company = $this->subscription->company;
|
||||
$user = $this->subscription->user;
|
||||
$user->setCompany($company);
|
||||
|
||||
$client_repo = new ClientRepository(new ClientContactRepository());
|
||||
|
||||
$data = [
|
||||
'name' => '',
|
||||
'contacts' => [
|
||||
['email' => $this->email],
|
||||
],
|
||||
'client_hash' => Str::random(40),
|
||||
'settings' => ClientSettings::defaults(),
|
||||
];
|
||||
|
||||
foreach ($this->request_data as $field => $value) {
|
||||
if (in_array($field, Client::$subscriptions_fillable)) {
|
||||
$data[$field] = $value;
|
||||
}
|
||||
|
||||
if (in_array($field, ClientContact::$subscription_fillable)) {
|
||||
$data['contacts'][0][$field] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// nlog($this->subscription->group_settings->settings);
|
||||
// nlog($this->subscription->group_settings->settings->currency_id);
|
||||
|
||||
if(array_key_exists('currency_id', $this->request_data)) {
|
||||
|
||||
$currency = Cache::get('currencies')->filter(function ($item){
|
||||
return $item->id == $this->request_data['currency_id'];
|
||||
})->first();
|
||||
|
||||
if($currency)
|
||||
$data['settings']->currency_id = $currency->id;
|
||||
|
||||
}
|
||||
elseif($this->subscription->group_settings && property_exists($this->subscription->group_settings->settings, 'currency_id')) {
|
||||
|
||||
$currency = Cache::get('currencies')->filter(function ($item){
|
||||
return $item->id == $this->subscription->group_settings->settings->currency_id;
|
||||
})->first();
|
||||
|
||||
if($currency)
|
||||
$data['settings']->currency_id = $currency->id;
|
||||
|
||||
}
|
||||
|
||||
if (array_key_exists('locale', $this->request_data)) {
|
||||
$request = $this->request_data;
|
||||
|
||||
$record = Cache::get('languages')->filter(function ($item) use ($request) {
|
||||
return $item->locale == $request['locale'];
|
||||
})->first();
|
||||
|
||||
if ($record) {
|
||||
$data['settings']['language_id'] = (string)$record->id;
|
||||
}
|
||||
}
|
||||
|
||||
$client = $client_repo->save($data, ClientFactory::create($company->id, $user->id));
|
||||
|
||||
return $client->fresh()->contacts->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetching payment methods from the client.
|
||||
*
|
||||
* @param ClientContact $contact
|
||||
* @return $this
|
||||
*/
|
||||
protected function getPaymentMethods(ClientContact $contact): self
|
||||
{
|
||||
Auth::guard('contact')->loginUsingId($contact->id, true);
|
||||
|
||||
$this->contact = $contact;
|
||||
|
||||
if ($this->subscription->trial_enabled) {
|
||||
$this->heading_text = ctrans('texts.plan_trial');
|
||||
$this->steps['show_start_trial'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ((int)$this->price == 0)
|
||||
$this->steps['payment_required'] = false;
|
||||
else
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
|
||||
|
||||
$this->heading_text = ctrans('texts.payment_methods');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Middle method between selecting payment method &
|
||||
* submitting the from to the backend.
|
||||
*
|
||||
* @param $company_gateway_id
|
||||
* @param $gateway_type_id
|
||||
*/
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
||||
{
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
$this->payment_method_id = $gateway_type_id;
|
||||
|
||||
$this->handleBeforePaymentEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to handle events before payments.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handleBeforePaymentEvents()
|
||||
{
|
||||
$this->steps['started_payment'] = true;
|
||||
$this->steps['show_loading_bar'] = true;
|
||||
|
||||
$data = [
|
||||
'client_id' => $this->contact->client->id,
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'invitations' => [[
|
||||
'key' => '',
|
||||
'client_contact_id' => $this->contact->hashed_id,
|
||||
]],
|
||||
'user_input_promo_code' => $this->coupon,
|
||||
'coupon' => empty($this->subscription->promo_code) ? '' : $this->coupon,
|
||||
'quantity' => $this->quantity,
|
||||
];
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
if (is_array($is_eligible) && $is_eligible['message'] != 'Success') {
|
||||
$this->steps['not_eligible'] = true;
|
||||
$this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
$this->steps['show_loading_bar'] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->invoice = $this->subscription
|
||||
->service()
|
||||
->createInvoice($data, $this->quantity)
|
||||
->service()
|
||||
->markSent()
|
||||
->fillDefaults()
|
||||
->adjustInventory()
|
||||
->save();
|
||||
|
||||
Cache::put($this->hash, [
|
||||
'subscription_id' => $this->subscription->id,
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'invoice_id' => $this->invoice->id,
|
||||
'context' => 'purchase',
|
||||
'campaign' => $this->campaign,
|
||||
], now()->addMinutes(60));
|
||||
|
||||
$this->emit('beforePaymentEventsCompleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy method for starting the trial.
|
||||
*
|
||||
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
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,
|
||||
]);
|
||||
}
|
||||
|
||||
public function handlePaymentNotRequired()
|
||||
{
|
||||
|
||||
$is_eligible = $this->subscription->service()->isEligible($this->contact);
|
||||
|
||||
if ($is_eligible['status_code'] != 200) {
|
||||
$this->steps['not_eligible'] = true;
|
||||
$this->steps['not_eligible_message'] = $is_eligible['message'];
|
||||
$this->steps['show_loading_bar'] = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
return $this->subscription->service()->handleNoPaymentRequired([
|
||||
'email' => $this->email ?? $this->contact->email,
|
||||
'quantity' => $this->quantity,
|
||||
'contact_id' => $this->contact->id,
|
||||
'client_id' => $this->contact->client->id,
|
||||
'coupon' => $this->coupon,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update quantity property.
|
||||
*
|
||||
* @param string $option
|
||||
* @return int
|
||||
*/
|
||||
public function updateQuantity(string $option): int
|
||||
{
|
||||
$this->handleCoupon();
|
||||
|
||||
if ($this->quantity == 1 && $option == 'decrement') {
|
||||
$this->price = $this->price * 1;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
if ($this->quantity > $this->subscription->max_seats_limit && $option == 'increment') {
|
||||
$this->price = $this->price * $this->subscription->max_seats_limit;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
if ($option == 'increment') {
|
||||
$this->quantity++;
|
||||
$this->price = $this->price * $this->quantity;
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
$this->quantity--;
|
||||
$this->price = $this->price * $this->quantity;
|
||||
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function handleCoupon()
|
||||
{
|
||||
|
||||
if($this->steps['discount_applied']){
|
||||
$this->price = $this->subscription->promo_price;
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->coupon == $this->subscription->promo_code) {
|
||||
$this->price = $this->subscription->promo_price;
|
||||
$this->quantity = 1;
|
||||
$this->steps['discount_applied'] = true;
|
||||
}
|
||||
else
|
||||
$this->price = $this->subscription->price;
|
||||
}
|
||||
|
||||
public function passwordlessLogin()
|
||||
{
|
||||
$this->passwordless_login_btn = true;
|
||||
|
||||
$contact = ClientContact::query()
|
||||
->where('email', $this->email)
|
||||
->where('company_id', $this->subscription->company_id)
|
||||
->first();
|
||||
|
||||
$mailer = new NinjaMailerObject();
|
||||
$mailer->mailable = new ContactPasswordlessLogin($this->email, $this->subscription->company, (string)route('client.subscription.purchase', $this->subscription->hashed_id) . '?coupon=' . $this->coupon);
|
||||
$mailer->company = $this->subscription->company;
|
||||
$mailer->settings = $this->subscription->company->settings;
|
||||
$mailer->to_user = $contact;
|
||||
|
||||
NinjaMailerJob::dispatch($mailer);
|
||||
|
||||
$this->steps['passwordless_login_sent'] = true;
|
||||
$this->passwordless_login_btn = false;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if (array_key_exists('email', $this->request_data)) {
|
||||
$this->email = $this->request_data['email'];
|
||||
}
|
||||
|
||||
if ($this->contact instanceof ClientContact) {
|
||||
$this->getPaymentMethods($this->contact);
|
||||
}
|
||||
|
||||
return render('components.livewire.billing-portal-purchasev2');
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ class ContactKeyLogin
|
||||
$request->session()->invalidate();
|
||||
}
|
||||
|
||||
//magic links survive for 1 hour
|
||||
if ($request->segment(2) && $request->segment(2) == 'magic_link' && $request->segment(3)) {
|
||||
$payload = Cache::get($request->segment(3));
|
||||
|
||||
@ -66,7 +67,11 @@ class ContactKeyLogin
|
||||
}
|
||||
} elseif ($request->segment(3) && config('ninja.db.multi_db_enabled')) {
|
||||
if (MultiDB::findAndSetDbByContactKey($request->segment(3))) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
}
|
||||
@ -82,7 +87,11 @@ class ContactKeyLogin
|
||||
}
|
||||
}
|
||||
} elseif ($request->segment(2) && $request->segment(2) == 'key_login' && $request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
$client_contact->save();
|
||||
@ -125,7 +134,11 @@ class ContactKeyLogin
|
||||
return redirect($this->setRedirectPath());
|
||||
}
|
||||
} elseif ($request->segment(3)) {
|
||||
if ($client_contact = ClientContact::where('contact_key', $request->segment(3))->first()) {
|
||||
if ($client_contact = ClientContact::with('company')->where('contact_key', $request->segment(3))->first()) {
|
||||
|
||||
if($client_contact->company->settings->enable_client_portal_password)
|
||||
return redirect()->route('client.login', ['company_key' => $client_contact->company->company_key]);
|
||||
|
||||
if (empty($client_contact->email)) {
|
||||
$client_contact->email = Str::random(6).'@example.com';
|
||||
$client_contact->save();
|
||||
|
@ -44,7 +44,7 @@ class StoreBankIntegrationRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(!array_key_exists('provider_name', $input) || strlen($input['provider_name']) == 0)
|
||||
if(!array_key_exists('provider_name', $input) || strlen($input['provider_name']) == 0 && array_key_exists('bank_account_name', $input))
|
||||
$input['provider_name'] = $input['bank_account_name'];
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -44,7 +44,7 @@ class StoreBankTransactionRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1)
|
||||
if(array_key_exists('bank_integration_id', $input) && strlen($input['bank_integration_id']) > 1 && !is_numeric($input['bank_integration_id']))
|
||||
$input['bank_integration_id'] = $this->decodePrimaryKey($input['bank_integration_id']);
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\BankTransactionRule;
|
||||
|
||||
class CreateBankTransactionRuleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', BankTransactionRule::class);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class DestroyBankTransactionRuleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->bank_transaction_rule);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class EditBankTransactionRuleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->bank_transaction_rule);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ShowBankTransactionRuleRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('view', $this->bank_transaction_rule);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class StoreBankTransactionRuleRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('create', BankTransactionRule::class);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
$rules = [
|
||||
'name' => 'bail|required|string',
|
||||
'rules' => 'bail|array',
|
||||
'rules.*.operator' => 'bail|required|nullable',
|
||||
'rules.*.search_key' => 'bail|required|nullable',
|
||||
'rules.*.value' => 'bail|required|nullable',
|
||||
'auto_convert' => 'bail|sometimes|bool',
|
||||
'matches_on_all' => 'bail|sometimes|bool',
|
||||
'applies_to' => 'bail|sometimes|string',
|
||||
];
|
||||
|
||||
if(isset($this->category_id))
|
||||
$rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
if(isset($this->vendor_id))
|
||||
$rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
if(isset($this->client_id))
|
||||
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\BankTransactionRule;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
class UpdateBankTransactionRuleRequest extends Request
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->can('edit', $this->bank_transaction_rule);
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
/* Ensure we have a client name, and that all emails are unique*/
|
||||
$rules = [
|
||||
'name' => 'bail|required|string',
|
||||
'rules' => 'bail|array',
|
||||
'rules.*.operator' => 'bail|required|nullable',
|
||||
'rules.*.search_key' => 'bail|required|nullable',
|
||||
'rules.*.value' => 'bail|required|nullable',
|
||||
'auto_convert' => 'bail|sometimes|bool',
|
||||
'matches_on_all' => 'bail|sometimes|bool',
|
||||
'applies_to' => 'bail|sometimes|string',
|
||||
];
|
||||
|
||||
if(isset($this->category_id))
|
||||
$rules['category_id'] = 'bail|sometimes|exists:expense_categories,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
if(isset($this->vendor_id))
|
||||
$rules['vendor_id'] = 'bail|sometimes|exists:vendors,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
if(isset($this->client_id))
|
||||
$rules['client_id'] = 'bail|sometimes|exists:clients,id,company_id,'.auth()->user()->company()->id.',is_deleted,0';
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
}
|
@ -61,22 +61,15 @@ class StoreCompanyRequest extends Request
|
||||
{
|
||||
$input = $this->all();
|
||||
|
||||
if(!isset($input['name']))
|
||||
$input['name'] = 'Untitled Company';
|
||||
|
||||
if (array_key_exists('google_analytics_url', $input)) {
|
||||
$input['google_analytics_key'] = $input['google_analytics_url'];
|
||||
}
|
||||
|
||||
// $company_settings = CompanySettings::defaults();
|
||||
|
||||
//@todo this code doesn't make sense as we never return $company_settings anywhere
|
||||
//@deprecated???
|
||||
// if (array_key_exists('settings', $input) && ! empty($input['settings'])) {
|
||||
// foreach ($input['settings'] as $key => $value) {
|
||||
// $company_settings->{$key} = $value;
|
||||
// }
|
||||
// }
|
||||
|
||||
if (array_key_exists('portal_domain', $input)) {
|
||||
$input['portal_domain'] = strtolower($input['portal_domain']);
|
||||
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
|
||||
}
|
||||
|
||||
$this->replace($input);
|
||||
|
@ -74,9 +74,9 @@ class UpdateCompanyRequest extends Request
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
if (Ninja::isHosted() && array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) {
|
||||
if (array_key_exists('portal_domain', $input) && strlen($input['portal_domain']) > 1) {
|
||||
$input['portal_domain'] = $this->addScheme($input['portal_domain']);
|
||||
$input['portal_domain'] = strtolower($input['portal_domain']);
|
||||
$input['portal_domain'] = rtrim(strtolower($input['portal_domain']), "/");
|
||||
}
|
||||
|
||||
if (array_key_exists('settings', $input)) {
|
||||
@ -108,7 +108,7 @@ class UpdateCompanyRequest extends Request
|
||||
}
|
||||
}
|
||||
|
||||
$settings['email_style_custom'] = str_replace("{{", "", $settings['email_style_custom']);
|
||||
$settings['email_style_custom'] = str_replace(['{{','}}'], ['',''], $settings['email_style_custom']);
|
||||
|
||||
if (! $account->isFreeHostedClient()) {
|
||||
return $settings;
|
||||
@ -127,9 +127,11 @@ class UpdateCompanyRequest extends Request
|
||||
|
||||
private function addScheme($url, $scheme = 'https://')
|
||||
{
|
||||
$url = str_replace('http://', '', $url);
|
||||
|
||||
$url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url;
|
||||
if(Ninja::isHosted())
|
||||
{
|
||||
$url = str_replace('http://', '', $url);
|
||||
$url = parse_url($url, PHP_URL_SCHEME) === null ? $scheme.$url : $url;
|
||||
}
|
||||
|
||||
return rtrim($url, '/');
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
||||
use App\Models\Gateway;
|
||||
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreCompanyGatewayRequest extends Request
|
||||
{
|
||||
@ -33,7 +34,7 @@ class StoreCompanyGatewayRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'gateway_key' => 'required|alpha_num',
|
||||
'gateway_key' => ['required','alpha_num',Rule::exists('gateways','key')],
|
||||
'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
|
||||
];
|
||||
|
||||
|
@ -32,8 +32,6 @@ class GenericReportRequest extends Request
|
||||
|
||||
return [
|
||||
'date_range' => 'bail|required|string',
|
||||
// 'start_date' => [Rule::requiredIf($this->date_range === 'custom')],
|
||||
// 'end_date' => [Rule::requiredIf($this->date_range === 'custom')],
|
||||
'end_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||
'report_keys' => 'present|array',
|
||||
@ -57,6 +55,12 @@ class GenericReportRequest extends Request
|
||||
$input['send_email'] = true;
|
||||
}
|
||||
|
||||
if (array_key_exists('date_range', $input) && $input['date_range'] != 'custom') {
|
||||
$input['start_date'] = null;
|
||||
$input['end_date'] = null;
|
||||
}
|
||||
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +125,10 @@ class Request extends FormRequest
|
||||
$input['company_gateway_id'] = $this->decodePrimaryKey($input['company_gateway_id']);
|
||||
}
|
||||
|
||||
if (array_key_exists('category_id', $input) && is_string($input['category_id'])) {
|
||||
$input['category_id'] = $this->decodePrimaryKey($input['category_id']);
|
||||
}
|
||||
|
||||
if (isset($input['client_contacts'])) {
|
||||
foreach ($input['client_contacts'] as $key => $contact) {
|
||||
if (! array_key_exists('send_email', $contact) || ! array_key_exists('id', $contact)) {
|
||||
|
@ -35,26 +35,34 @@ class StoreSubscriptionRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'product_ids' => ['sometimes'],
|
||||
'recurring_product_ids' => ['sometimes'],
|
||||
'assigned_user_id' => ['sometimes'],
|
||||
'is_recurring' => ['sometimes'],
|
||||
'frequency_id' => ['required_with:recurring_product_ids'],
|
||||
'auto_bill' => ['sometimes'],
|
||||
'promo_code' => ['sometimes'],
|
||||
'promo_discount' => ['sometimes'],
|
||||
'is_amount_discount' => ['sometimes'],
|
||||
'allow_cancellation' => ['sometimes'],
|
||||
'per_set_enabled' => ['sometimes'],
|
||||
'min_seats_limit' => ['sometimes'],
|
||||
'max_seats_limit' => ['sometimes'],
|
||||
'trial_enabled' => ['sometimes'],
|
||||
'trial_duration' => ['sometimes'],
|
||||
'allow_query_overrides' => ['sometimes'],
|
||||
'allow_plan_changes' => ['sometimes'],
|
||||
'refund_period' => ['sometimes'],
|
||||
'webhook_configuration' => ['array'],
|
||||
'name' => ['required', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)],
|
||||
'group_id' => ['bail','sometimes', 'nullable', Rule::exists('group_settings','id')->where('company_id', auth()->user()->company()->id)],
|
||||
'assigned_user_id' => ['bail','sometimes', 'nullable', Rule::exists('users','id')->where('account_id', auth()->user()->account_id)],
|
||||
'product_ids' => 'bail|sometimes|nullable|string',
|
||||
'recurring_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'is_recurring' => 'bail|sometimes|bool',
|
||||
'frequency_id' => 'bail|required_with:recurring_product_ids',
|
||||
'auto_bill' => 'bail|sometimes|nullable|string',
|
||||
'promo_code' => 'bail|sometimes|nullable|string',
|
||||
'promo_discount' => 'bail|sometimes|numeric',
|
||||
'is_amount_discount' => 'bail|sometimes|bool',
|
||||
'allow_cancellation' => 'bail|sometimes|bool',
|
||||
'per_set_enabled' => 'bail|sometimes|bool',
|
||||
'min_seats_limit' => 'bail|sometimes|numeric',
|
||||
'max_seats_limit' => 'bail|sometimes|numeric',
|
||||
'trial_enabled' => 'bail|sometimes|bool',
|
||||
'trial_duration' => 'bail|sometimes|numeric',
|
||||
'allow_query_overrides' => 'bail|sometimes|bool',
|
||||
'allow_plan_changes' => 'bail|sometimes|bool',
|
||||
'refund_period' => 'bail|sometimes|numeric',
|
||||
'webhook_configuration' => 'bail|array',
|
||||
'webhook_configuration.post_purchase_url' => 'bail|sometimes|nullable|string',
|
||||
'webhook_configuration.post_purchase_rest_method' => 'bail|sometimes|nullable|string',
|
||||
'webhook_configuration.post_purchase_headers' => 'bail|sometimes|array',
|
||||
'registration_required' => 'bail|sometimes|bool',
|
||||
'optional_recurring_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'optional_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'use_inventory_management' => 'bail|sometimes|bool'
|
||||
];
|
||||
|
||||
return $this->globalRules($rules);
|
||||
|
@ -37,26 +37,34 @@ class UpdateSubscriptionRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$rules = [
|
||||
'product_ids' => ['sometimes'],
|
||||
'recurring_product_ids' => ['sometimes'],
|
||||
'assigned_user_id' => ['sometimes'],
|
||||
'is_recurring' => ['sometimes'],
|
||||
'frequency_id' => ['required_with:recurring_product_ids'],
|
||||
'auto_bill' => ['sometimes'],
|
||||
'promo_code' => ['sometimes'],
|
||||
'promo_discount' => ['sometimes'],
|
||||
'is_amount_discount' => ['sometimes'],
|
||||
'allow_cancellation' => ['sometimes'],
|
||||
'per_set_enabled' => ['sometimes'],
|
||||
'min_seats_limit' => ['sometimes'],
|
||||
'max_seats_limit' => ['sometimes'],
|
||||
'trial_enabled' => ['sometimes'],
|
||||
'trial_duration' => ['sometimes'],
|
||||
'allow_query_overrides' => ['sometimes'],
|
||||
'allow_plan_changes' => ['sometimes'],
|
||||
'refund_period' => ['sometimes'],
|
||||
'webhook_configuration' => ['array'],
|
||||
'name' => ['sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)],
|
||||
'name' => ['bail','sometimes', Rule::unique('subscriptions')->where('company_id', auth()->user()->company()->id)->ignore($this->subscription->id)],
|
||||
'group_id' => ['bail','sometimes', 'nullable', Rule::exists('group_settings','id')->where('company_id', auth()->user()->company()->id)],
|
||||
'assigned_user_id' => ['bail','sometimes', 'nullable', Rule::exists('users','id')->where('account_id', auth()->user()->account_id)],
|
||||
'product_ids' => 'bail|sometimes|nullable|string',
|
||||
'recurring_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'is_recurring' => 'bail|sometimes|bool',
|
||||
'frequency_id' => 'bail|required_with:recurring_product_ids',
|
||||
'auto_bill' => 'bail|sometimes|nullable|string',
|
||||
'promo_code' => 'bail|sometimes|nullable|string',
|
||||
'promo_discount' => 'bail|sometimes|numeric',
|
||||
'is_amount_discount' => 'bail|sometimes|bool',
|
||||
'allow_cancellation' => 'bail|sometimes|bool',
|
||||
'per_set_enabled' => 'bail|sometimes|bool',
|
||||
'min_seats_limit' => 'bail|sometimes|numeric',
|
||||
'max_seats_limit' => 'bail|sometimes|numeric',
|
||||
'trial_enabled' => 'bail|sometimes|bool',
|
||||
'trial_duration' => 'bail|sometimes|numeric',
|
||||
'allow_query_overrides' => 'bail|sometimes|bool',
|
||||
'allow_plan_changes' => 'bail|sometimes|bool',
|
||||
'refund_period' => 'bail|sometimes|numeric',
|
||||
'webhook_configuration' => 'bail|array',
|
||||
'webhook_configuration.post_purchase_url' => 'bail|sometimes|nullable|string',
|
||||
'webhook_configuration.post_purchase_rest_method' => 'bail|sometimes|nullable|string',
|
||||
'webhook_configuration.post_purchase_headers' => 'bail|sometimes|array',
|
||||
'registration_required' => 'bail|sometimes|bool',
|
||||
'optional_recurring_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'optional_product_ids' => 'bail|sometimes|nullable|string',
|
||||
'use_inventory_management' => 'bail|sometimes|bool',
|
||||
];
|
||||
|
||||
return $this->globalRules($rules);
|
||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
||||
use App\Models\Project;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateTaskRequest extends Request
|
||||
@ -29,6 +30,10 @@ class UpdateTaskRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
//prevent locked tasks from updating
|
||||
if($this->task->invoice_id && $this->task->company->invoice_task_lock)
|
||||
return false;
|
||||
|
||||
return auth()->user()->can('edit', $this->task);
|
||||
}
|
||||
|
||||
@ -87,4 +92,11 @@ class UpdateTaskRequest extends Request
|
||||
|
||||
$this->replace($input);
|
||||
}
|
||||
|
||||
|
||||
protected function failedAuthorization()
|
||||
{
|
||||
throw new AuthorizationException(ctrans('texts.task_update_authorization_error'));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ class ConfirmSmsRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules()
|
||||
|
@ -24,7 +24,7 @@ class GenerateSmsRequest extends Request
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@ -26,6 +26,7 @@ class ValidCompanyQuantity implements Rule
|
||||
*/
|
||||
public function passes($attribute, $value)
|
||||
{
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
return auth()->user()->company()->account->companies->count() < 10;
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ use App\Repositories\PaymentRepository;
|
||||
use App\Repositories\ProductRepository;
|
||||
use App\Repositories\QuoteRepository;
|
||||
use App\Repositories\VendorRepository;
|
||||
use App\Services\Bank\BankMatchingService;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
@ -107,6 +108,8 @@ class Csv extends BaseImport implements ImportInterface
|
||||
$bank_transaction_count = $this->ingest($data, $entity_type);
|
||||
$this->entity_count['bank_transactions'] = $bank_transaction_count;
|
||||
|
||||
BankMatchingService::dispatchSync($this->company->id, $this->company->db);
|
||||
|
||||
}
|
||||
|
||||
public function client()
|
||||
|
@ -59,7 +59,7 @@ class BankTransformer extends BaseTransformer
|
||||
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit'))
|
||||
return 'CREDIT';
|
||||
|
||||
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.bank_type']) == 'withdrawal'))
|
||||
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'DEBIT') || strtolower($transaction['transaction.base_type']) == 'withdrawal'))
|
||||
return 'DEBIT';
|
||||
|
||||
if(array_key_exists('transaction.category_id', $transaction))
|
||||
|
@ -196,13 +196,19 @@ class MatchBankTransactions implements ShouldQueue
|
||||
$expense->payment_date = Carbon::parse($this->bt->date);
|
||||
$expense->transaction_reference = $this->bt->description;
|
||||
$expense->transaction_id = $this->bt->id;
|
||||
$expense->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null;
|
||||
|
||||
if(array_key_exists('vendor_id', $input))
|
||||
$expense->vendor_id = $input['vendor_id'];
|
||||
|
||||
$expense->invoice_documents = $this->company->invoice_expense_documents;
|
||||
$expense->should_be_invoiced = $this->company->mark_expenses_invoiceable;
|
||||
$expense->save();
|
||||
|
||||
$this->bt->expense_id = $expense->id;
|
||||
$this->bt->vendor_id = array_key_exists('vendor_id', $input) ? $input['vendor_id'] : null;
|
||||
|
||||
if(array_key_exists('vendor_id', $input))
|
||||
$this->bt->vendor_id = $input['vendor_id'];
|
||||
|
||||
$this->bt->status_id = BankTransaction::STATUS_CONVERTED;
|
||||
$this->bt->save();
|
||||
|
||||
@ -254,6 +260,9 @@ class MatchBankTransactions implements ShouldQueue
|
||||
|
||||
}, 1);
|
||||
|
||||
if(!$this->invoice)
|
||||
return;
|
||||
|
||||
/* Create Payment */
|
||||
$payment = PaymentFactory::create($this->invoice->company_id, $this->invoice->user_id);
|
||||
|
||||
|
@ -36,6 +36,8 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
class CompanyExport implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||
@ -531,6 +533,15 @@ class CompanyExport implements ShouldQueue
|
||||
|
||||
$path = 'backups';
|
||||
|
||||
Storage::makeDirectory(public_path('storage/backups/'));
|
||||
|
||||
try {
|
||||
mkdir(public_path('storage/backups/'));
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
nlog("could not create directory");
|
||||
}
|
||||
|
||||
$zip_path = public_path('storage/backups/'.$file_name);
|
||||
$zip = new \ZipArchive();
|
||||
|
||||
|
@ -62,8 +62,15 @@ class AutoBillCron
|
||||
|
||||
nlog($auto_bill_partial_invoices->count().' partial invoices to auto bill');
|
||||
|
||||
$auto_bill_partial_invoices->cursor()->each(function ($invoice) {
|
||||
AutoBill::dispatch($invoice->id, false);
|
||||
$auto_bill_partial_invoices->chunk(100, function ($invoices) {
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
AutoBill::dispatch($invoice->id, false);
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
|
||||
});
|
||||
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
@ -79,8 +86,15 @@ class AutoBillCron
|
||||
|
||||
nlog($auto_bill_invoices->count().' full invoices to auto bill');
|
||||
|
||||
$auto_bill_invoices->cursor()->each(function ($invoice) {
|
||||
AutoBill::dispatch($invoice->id, false);
|
||||
$auto_bill_invoices->chunk(100, function ($invoices) {
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
AutoBill::dispatch($invoice->id, false);
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
|
||||
});
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
@ -100,8 +114,14 @@ class AutoBillCron
|
||||
|
||||
nlog($auto_bill_partial_invoices->count()." partial invoices to auto bill db = {$db}");
|
||||
|
||||
$auto_bill_partial_invoices->cursor()->each(function ($invoice) use ($db) {
|
||||
AutoBill::dispatch($invoice->id, $db);
|
||||
$auto_bill_partial_invoices->chunk(100, function ($invoices) use($db){
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
AutoBill::dispatch($invoice->id, $db);
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
});
|
||||
|
||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||
@ -117,10 +137,15 @@ class AutoBillCron
|
||||
|
||||
nlog($auto_bill_invoices->count()." full invoices to auto bill db = {$db}");
|
||||
|
||||
$auto_bill_invoices->cursor()->each(function ($invoice) use ($db) {
|
||||
nlog($this->counter);
|
||||
AutoBill::dispatch($invoice->id, $db);
|
||||
$this->counter++;
|
||||
$auto_bill_invoices->chunk(100, function ($invoices) use($db){
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
AutoBill::dispatch($invoice->id, $db);
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -46,39 +46,62 @@ class RecurringExpensesCron
|
||||
nlog('Sending recurring expenses '.Carbon::now()->format('Y-m-d h:i:s'));
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->getRecurringExpenses();
|
||||
|
||||
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
||||
nlog(now()->format('Y-m-d').' Generating Recurring Expenses. Count = '.$recurring_expenses->count());
|
||||
|
||||
$recurring_expenses->each(function ($recurring_expense, $key) {
|
||||
nlog('Current date = '.now()->format('Y-m-d').' Recurring date = '.$recurring_expense->next_send_date);
|
||||
|
||||
if (! $recurring_expense->company->is_disabled) {
|
||||
$this->generateExpense($recurring_expense);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->getRecurringExpenses();
|
||||
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
||||
nlog(now()->format('Y-m-d').' Generating Recurring Expenses. Count = '.$recurring_expenses->count());
|
||||
|
||||
$recurring_expenses->each(function ($recurring_expense, $key) {
|
||||
nlog('Current date = '.now()->format('Y-m-d').' Recurring date = '.$recurring_expense->next_send_date);
|
||||
|
||||
if (! $recurring_expense->company->is_disabled) {
|
||||
$this->generateExpense($recurring_expense);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getRecurringExpenses()
|
||||
{
|
||||
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereNotNull('next_send_date')
|
||||
->whereNull('deleted_at')
|
||||
->where('status_id', RecurringInvoice::STATUS_ACTIVE)
|
||||
->where('remaining_cycles', '!=', '0')
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('company')
|
||||
->cursor();
|
||||
|
||||
nlog(now()->format('Y-m-d').' Generating Recurring Expenses. Count = '.$recurring_expenses->count());
|
||||
|
||||
$recurring_expenses->each(function ($recurring_expense, $key) {
|
||||
nlog('Current date = '.now()->format('Y-m-d').' Recurring date = '.$recurring_expense->next_send_date);
|
||||
|
||||
if (! $recurring_expense->company->is_disabled) {
|
||||
$this->generateExpense($recurring_expense);
|
||||
}
|
||||
});
|
||||
//extracting this back to the if/else block to test duplicate crons
|
||||
}
|
||||
|
||||
private function generateExpense(RecurringExpense $recurring_expense)
|
||||
|
@ -41,44 +41,62 @@ class SubscriptionCron
|
||||
nlog('Subscription Cron');
|
||||
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->loopSubscriptions();
|
||||
|
||||
$invoices = Invoice::where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
$subscription = $invoice->subscription;
|
||||
|
||||
$body = [
|
||||
'context' => 'plan_expired',
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'subscription' => $subscription->hashed_id,
|
||||
];
|
||||
|
||||
$this->sendLoad($subscription, $body);
|
||||
//This will send the notification daily.
|
||||
//We'll need to handle this by performing some action on the invoice to either archive it or delete it?
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->loopSubscriptions();
|
||||
$invoices = Invoice::where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
$subscription = $invoice->subscription;
|
||||
|
||||
$body = [
|
||||
'context' => 'plan_expired',
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'subscription' => $subscription->hashed_id,
|
||||
];
|
||||
|
||||
$this->sendLoad($subscription, $body);
|
||||
//This will send the notification daily.
|
||||
//We'll need to handle this by performing some action on the invoice to either archive it or delete it?
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function loopSubscriptions()
|
||||
{
|
||||
$invoices = Invoice::where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->where('balance', '>', 0)
|
||||
->whereDate('due_date', '<=', now()->addDay()->startOfDay())
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('subscription_id')
|
||||
->cursor();
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
$subscription = $invoice->subscription;
|
||||
|
||||
$body = [
|
||||
'context' => 'plan_expired',
|
||||
'client' => $invoice->client->hashed_id,
|
||||
'invoice' => $invoice->hashed_id,
|
||||
'subscription' => $subscription->hashed_id,
|
||||
];
|
||||
|
||||
$this->sendLoad($subscription, $body);
|
||||
//This will send the notification daily.
|
||||
//We'll need to handle this by performing some action on the invoice to either archive it or delete it?
|
||||
});
|
||||
}
|
||||
|
||||
private function handleWebhook($invoice, $subscription)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -99,11 +99,11 @@ class EmailEntity implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :void
|
||||
{
|
||||
/* Don't fire emails if the company is disabled */
|
||||
if ($this->company->is_disabled) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set DB */
|
||||
|
@ -44,38 +44,30 @@ class BankTransactionSync implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// if (! Ninja::isHosted()) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
foreach (MultiDB::$dbs as $db)
|
||||
{
|
||||
MultiDB::setDB($db);
|
||||
|
||||
nlog("syncing transactions");
|
||||
|
||||
$this->syncTransactions();
|
||||
$a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){
|
||||
|
||||
// $queue = Ninja::isHosted() ? 'bank' : 'default';
|
||||
|
||||
if($account->isPaid() && $account->plan == 'enterprise')
|
||||
{
|
||||
|
||||
$account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account){
|
||||
|
||||
ProcessBankTransactions::dispatchSync($account->bank_integration_account_id, $bank_integration);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function syncTransactions()
|
||||
{
|
||||
$a = Account::with('bank_integrations')->whereNotNull('bank_integration_account_id')->cursor()->each(function ($account){
|
||||
|
||||
// $queue = Ninja::isHosted() ? 'bank' : 'default';
|
||||
|
||||
if($account->isPaid() && $account->plan == 'enterprise')
|
||||
{
|
||||
|
||||
$account->bank_integrations()->where('auto_sync', true)->cursor()->each(function ($bank_integration) use ($account){
|
||||
|
||||
ProcessBankTransactions::dispatchSync($account->bank_integration_account_id, $bank_integration);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,39 +42,54 @@ class CompanySizeCheck implements ShouldQueue
|
||||
public function handle()
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->check();
|
||||
|
||||
Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) {
|
||||
if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) {
|
||||
nlog("Marking company {$company->id} as large");
|
||||
|
||||
$company->account->companies()->update(['is_large' => true]);
|
||||
}
|
||||
});
|
||||
|
||||
nlog("updating all client credit balances");
|
||||
|
||||
Client::where('updated_at', '>', now()->subDay())
|
||||
->cursor()
|
||||
->each(function ($client){
|
||||
|
||||
$client->credit_balance = $client->service()->getCreditBalance();
|
||||
$client->save();
|
||||
|
||||
});
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$this->check();
|
||||
nlog("Company size check db {$db}");
|
||||
|
||||
Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) {
|
||||
if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) {
|
||||
nlog("Marking company {$company->id} as large");
|
||||
|
||||
$company->account->companies()->update(['is_large' => true]);
|
||||
}
|
||||
});
|
||||
|
||||
nlog("updating all client credit balances");
|
||||
|
||||
Client::where('updated_at', '>', now()->subDay())
|
||||
->cursor()
|
||||
->each(function ($client){
|
||||
|
||||
$client->credit_balance = $client->service()->getCreditBalance();
|
||||
$client->save();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function check()
|
||||
{
|
||||
nlog("Checking all company sizes");
|
||||
|
||||
Company::where('is_large', false)->withCount(['invoices', 'clients', 'products'])->cursor()->each(function ($company) {
|
||||
if ($company->invoices_count > 500 || $company->products_count > 500 || $company->clients_count > 500) {
|
||||
nlog("Marking company {$company->id} as large");
|
||||
|
||||
$company->account->companies()->update(['is_large' => true]);
|
||||
}
|
||||
});
|
||||
|
||||
nlog("updating all client credit balances");
|
||||
|
||||
Client::where('updated_at', '>', now()->subDay())
|
||||
->cursor()
|
||||
->each(function ($client){
|
||||
|
||||
$client->credit_balance = $client->service()->getCreditBalance();
|
||||
$client->save();
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ class QueueSize implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :void
|
||||
{
|
||||
LightLogs::create(new QueueSizeAnalytic(Queue::size()))
|
||||
->send();
|
||||
|
@ -49,10 +49,6 @@ class SystemMaintenance implements ShouldQueue
|
||||
|
||||
nlog('Starting System Maintenance');
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$delete_pdf_days = config('ninja.maintenance.delete_pdfs');
|
||||
|
||||
nlog("Number of days to keep PDFs {$delete_pdf_days}");
|
||||
|
@ -42,39 +42,61 @@ class QuoteCheckExpired implements ShouldQueue
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
return $this->checkForExpiredQuotes();
|
||||
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
if (! config('ninja.db.multi_db_enabled')){
|
||||
|
||||
MultiDB::setDB($db);
|
||||
Quote::query()
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($quote){
|
||||
$this->queueExpiredQuoteNotification($quote);
|
||||
});
|
||||
|
||||
$this->checkForExpiredQuotes();
|
||||
}
|
||||
else {
|
||||
|
||||
foreach (MultiDB::$dbs as $db)
|
||||
{
|
||||
|
||||
MultiDB::setDB($db);
|
||||
|
||||
Quote::query()
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($quote){
|
||||
$this->queueExpiredQuoteNotification($quote);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function checkForExpiredQuotes()
|
||||
{
|
||||
Quote::query()
|
||||
->where('status_id', Quote::STATUS_SENT)
|
||||
->where('is_deleted', false)
|
||||
->whereNull('deleted_at')
|
||||
->whereNotNull('due_date')
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
// ->where('due_date', '<='. now()->toDateTimeString())
|
||||
->whereBetween('due_date', [now()->subDay()->startOfDay(), now()->startOfDay()->subSecond()])
|
||||
->cursor()
|
||||
->each(function ($quote){
|
||||
$this->queueExpiredQuoteNotification($quote);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function queueExpiredQuoteNotification(Quote $quote)
|
||||
|
@ -44,25 +44,17 @@ class ReminderJob implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
public function handle() :void
|
||||
{
|
||||
if (! config('ninja.db.multi_db_enabled')) {
|
||||
$this->processReminders();
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
nlog("set db {$db}");
|
||||
$this->processReminders();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processReminders()
|
||||
{
|
||||
nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s'));
|
||||
set_time_limit(0);
|
||||
|
||||
Invoice::query()
|
||||
if (! config('ninja.db.multi_db_enabled'))
|
||||
{
|
||||
|
||||
nlog("Sending invoice reminders on ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Invoice::query()
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->whereNull('deleted_at')
|
||||
@ -75,44 +67,101 @@ class ReminderJob implements ShouldQueue
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('invitations')->cursor()->each(function ($invoice) {
|
||||
if ($invoice->isPayable()) {
|
||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||
nlog("reminder template = {$reminder_template}");
|
||||
$invoice->service()->touchReminder($reminder_template)->save();
|
||||
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
||||
->with('invitations')->chunk(50, function ($invoices) {
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
$this->sendReminderForInvoice($invoice);
|
||||
}
|
||||
|
||||
$invoice->service()->touchPdf();
|
||||
sleep(2);
|
||||
|
||||
//20-04-2022 fixes for endless reminders - generic template naming was wrong
|
||||
$enabled_reminder = 'enable_'.$reminder_template;
|
||||
|
||||
if ($reminder_template == 'endless_reminder') {
|
||||
$enabled_reminder = 'enable_reminder_endless';
|
||||
}
|
||||
|
||||
//check if this reminder needs to be emailed
|
||||
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
||||
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) &&
|
||||
$invoice->client->getSetting($enabled_reminder) &&
|
||||
$invoice->client->getSetting('send_reminders') &&
|
||||
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
|
||||
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
EmailEntity::dispatchSync($invitation, $invitation->company, $reminder_template);
|
||||
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
|
||||
});
|
||||
|
||||
if ($invoice->invitations->count() > 0) {
|
||||
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
|
||||
}
|
||||
}
|
||||
$invoice->service()->setReminder()->save();
|
||||
} else {
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->save();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
//multiDB environment, need to
|
||||
|
||||
foreach (MultiDB::$dbs as $db)
|
||||
{
|
||||
|
||||
MultiDB::setDB($db);
|
||||
|
||||
nlog("Sending invoice reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
|
||||
|
||||
Invoice::query()
|
||||
->where('is_deleted', 0)
|
||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||
->whereNull('deleted_at')
|
||||
->where('balance', '>', 0)
|
||||
->where('next_send_date', '<=', now()->toDateTimeString())
|
||||
->whereHas('client', function ($query) {
|
||||
$query->where('is_deleted', 0)
|
||||
->where('deleted_at', null);
|
||||
})
|
||||
->whereHas('company', function ($query) {
|
||||
$query->where('is_disabled', 0);
|
||||
})
|
||||
->with('invitations')->chunk(50, function ($invoices) {
|
||||
// if ($invoice->refresh() && $invoice->isPayable()) {
|
||||
|
||||
foreach($invoices as $invoice)
|
||||
{
|
||||
$this->sendReminderForInvoice($invoice);
|
||||
}
|
||||
|
||||
sleep(2);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private function sendReminderForInvoice($invoice) {
|
||||
|
||||
if ($invoice->isPayable()) {
|
||||
|
||||
//Attempts to prevent duplicates from sending
|
||||
if($invoice->reminder_last_sent && Carbon::parse($invoice->reminder_last_sent)->startOfDay()->eq(now()->startOfDay())){
|
||||
nlog("caught a duplicate reminder for invoice {$invoice->number}");
|
||||
return;
|
||||
}
|
||||
|
||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||
nlog("reminder template = {$reminder_template}");
|
||||
$invoice->service()->touchReminder($reminder_template)->save();
|
||||
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
||||
|
||||
//20-04-2022 fixes for endless reminders - generic template naming was wrong
|
||||
$enabled_reminder = 'enable_'.$reminder_template;
|
||||
if ($reminder_template == 'endless_reminder') {
|
||||
$enabled_reminder = 'enable_reminder_endless';
|
||||
}
|
||||
|
||||
//check if this reminder needs to be emailed
|
||||
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
||||
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) &&
|
||||
$invoice->client->getSetting($enabled_reminder) &&
|
||||
$invoice->client->getSetting('send_reminders') &&
|
||||
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
|
||||
|
||||
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template)->delay(now()->addSeconds(3));
|
||||
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
|
||||
});
|
||||
|
||||
if ($invoice->invitations->count() > 0) {
|
||||
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
|
||||
}
|
||||
}
|
||||
$invoice->service()->setReminder()->save();
|
||||
} else {
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,28 +245,25 @@ class ReminderJob implements ShouldQueue
|
||||
$invoice->line_items = $invoice_items;
|
||||
|
||||
/**Refresh Invoice values*/
|
||||
$invoice->calc()->getInvoice()->save();
|
||||
$invoice->fresh();
|
||||
$invoice->service()->deletePdf();
|
||||
|
||||
/* Refresh the client here to ensure the balance is fresh */
|
||||
$client = $invoice->client;
|
||||
$client = $client->fresh();
|
||||
$invoice = $invoice->calc()->getInvoice();
|
||||
// $invoice->service()->deletePdf(); 24-11-2022 no need to delete here because we regenerate later anyway
|
||||
|
||||
nlog('adjusting client balance and invoice balance by #'.$invoice->number.' '.($invoice->balance - $temp_invoice_balance));
|
||||
$client->service()->updateBalance($invoice->balance - $temp_invoice_balance)->save();
|
||||
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance);
|
||||
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
||||
|
||||
$transaction = [
|
||||
'invoice' => $invoice->transaction_event(),
|
||||
'payment' => [],
|
||||
'client' => $client->transaction_event(),
|
||||
'credit' => [],
|
||||
'metadata' => ['setLateFee'],
|
||||
];
|
||||
// $transaction = [
|
||||
// 'invoice' => $invoice->transaction_event(),
|
||||
// 'payment' => [],
|
||||
// 'client' => $invoice->client->transaction_event(),
|
||||
// 'credit' => [],
|
||||
// 'metadata' => ['setLateFee'],
|
||||
// ];
|
||||
|
||||
// TransactionLog::dispatch(TransactionEvent::CLIENT_STATUS, $transaction, $invoice->company->db);
|
||||
|
||||
$invoice->service()->touchPdf(true);
|
||||
|
||||
return $invoice;
|
||||
}
|
||||
}
|
||||
|
@ -34,19 +34,7 @@ class UpdateExchangeRates implements ShouldQueue
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
$this->updateCurrencies();
|
||||
}
|
||||
} else {
|
||||
$this->updateCurrencies();
|
||||
}
|
||||
}
|
||||
|
||||
private function updateCurrencies()
|
||||
public function handle() :void
|
||||
{
|
||||
info('updating currencies');
|
||||
|
||||
@ -56,20 +44,47 @@ class UpdateExchangeRates implements ShouldQueue
|
||||
|
||||
$cc_endpoint = sprintf('https://openexchangerates.org/api/latest.json?app_id=%s', config('ninja.currency_converter_api_key'));
|
||||
|
||||
$client = new Client();
|
||||
$response = $client->get($cc_endpoint);
|
||||
if (config('ninja.db.multi_db_enabled')) {
|
||||
foreach (MultiDB::$dbs as $db) {
|
||||
MultiDB::setDB($db);
|
||||
|
||||
$client = new Client();
|
||||
$response = $client->get($cc_endpoint);
|
||||
|
||||
$currency_api = json_decode($response->getBody());
|
||||
$currency_api = json_decode($response->getBody());
|
||||
|
||||
/* Update all currencies */
|
||||
Currency::all()->each(function ($currency) use ($currency_api) {
|
||||
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||
$currency->save();
|
||||
});
|
||||
/* Update all currencies */
|
||||
Currency::all()->each(function ($currency) use ($currency_api) {
|
||||
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||
$currency->save();
|
||||
});
|
||||
|
||||
/* Rebuild the cache */
|
||||
$currencies = Currency::orderBy('name')->get();
|
||||
/* Rebuild the cache */
|
||||
$currencies = Currency::orderBy('name')->get();
|
||||
|
||||
Cache::forever('currencies', $currencies);
|
||||
Cache::forever('currencies', $currencies);
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
$client = new Client();
|
||||
$response = $client->get($cc_endpoint);
|
||||
|
||||
$currency_api = json_decode($response->getBody());
|
||||
|
||||
/* Update all currencies */
|
||||
Currency::all()->each(function ($currency) use ($currency_api) {
|
||||
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||
$currency->save();
|
||||
});
|
||||
|
||||
/* Rebuild the cache */
|
||||
$currencies = Currency::orderBy('name')->get();
|
||||
|
||||
Cache::forever('currencies', $currencies);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,13 +37,14 @@ class VersionCheck implements ShouldQueue
|
||||
{
|
||||
$version_file = trim(@file_get_contents(config('ninja.version_url')));
|
||||
|
||||
nlog("latest version = {$version_file}");
|
||||
|
||||
if (Ninja::isSelfHost() && $version_file) {
|
||||
Account::whereNotNull('id')->update(['latest_version' => $version_file]);
|
||||
}
|
||||
|
||||
if (Ninja::isSelfHost()) {
|
||||
|
||||
nlog("latest version = {$version_file}");
|
||||
|
||||
$account = Account::first();
|
||||
|
||||
if (! $account) {
|
||||
|
@ -29,8 +29,6 @@ class InvoiceFailedEmailNotification
|
||||
|
||||
use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $delay = 10;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
@ -48,7 +46,7 @@ class InvoiceFailedEmailNotification
|
||||
$first_notification_sent = true;
|
||||
|
||||
$invoice = $event->invitation->invoice;
|
||||
$invoice->update(['last_sent_date' => now()]);
|
||||
// $invoice->update(['last_sent_date' => now()]);
|
||||
|
||||
$nmo = new NinjaMailerObject;
|
||||
$nmo->mailable = new NinjaMailer((new EntityFailedSendObject($event->invitation, 'invoice', $event->template, $event->message))->build());
|
||||
|
@ -292,6 +292,14 @@ class Activity extends StaticModel
|
||||
return $this->belongsTo(Quote::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function subscription()
|
||||
{
|
||||
return $this->belongsTo(Subscription::class)->withTrashed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Filterable;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Bank\BankService;
|
||||
|
171
app/Models/BankTransactionRule.php
Normal file
171
app/Models/BankTransactionRule.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Filterable;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class BankTransactionRule extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
use MakesHash;
|
||||
use Filterable;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'rules',
|
||||
'auto_convert',
|
||||
'matches_on_all',
|
||||
'applies_to',
|
||||
'client_id',
|
||||
'vendor_id',
|
||||
'category_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'rules' => 'array',
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
'deleted_at' => 'timestamp',
|
||||
];
|
||||
|
||||
protected $dates = [
|
||||
];
|
||||
|
||||
protected array $search_keys = [
|
||||
'description' => 'string',
|
||||
'amount' => 'number',
|
||||
];
|
||||
|
||||
/* Amount */
|
||||
protected array $number_operators = [
|
||||
'=',
|
||||
'>',
|
||||
'>=',
|
||||
'<',
|
||||
'<='
|
||||
];
|
||||
|
||||
/* Description, Client, Vendor, Reference Number */
|
||||
protected array $string_operators = [
|
||||
'is',
|
||||
'contains',
|
||||
'starts_with',
|
||||
'is_empty',
|
||||
];
|
||||
|
||||
private array $search_results = [];
|
||||
|
||||
// rule object looks like this:
|
||||
//[
|
||||
// {
|
||||
// 'search_key': 'client_id',
|
||||
// 'operator' : 'is',
|
||||
// 'value' : 'Sparky'
|
||||
// }
|
||||
//]
|
||||
|
||||
// public function processRule(BankTransaction $bank_transaction)
|
||||
// {
|
||||
// foreach($this->rules as $key => $rule)
|
||||
// {
|
||||
// $this->search($rule, $key, $bank_transaction);
|
||||
// }
|
||||
// }
|
||||
|
||||
// private function search($rule, $key, $bank_transaction)
|
||||
// {
|
||||
// if($rule->search_key == 'amount')
|
||||
// {
|
||||
// //number search
|
||||
// }
|
||||
// else {
|
||||
// //string search
|
||||
// }
|
||||
// }
|
||||
|
||||
// private function findAmount($amount, $bank_transaction)
|
||||
// {
|
||||
// if($bank_transaction->base_type == 'CREDIT'){
|
||||
// //search invoices
|
||||
// }
|
||||
// else{
|
||||
// //search expenses
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// private function searchClient($rule, $bank_transaction)
|
||||
// {
|
||||
// if($bank_transaction->base_type == 'CREDIT'){
|
||||
// //search invoices
|
||||
// }
|
||||
// else{
|
||||
// //search expenses
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// private function searchVendor($rule, $bank_transaction)
|
||||
// {
|
||||
// //search expenses
|
||||
|
||||
|
||||
// }
|
||||
|
||||
// private function searchDescription($rule, $bank_transaction)
|
||||
// {
|
||||
// //search expenses public notes
|
||||
// }
|
||||
|
||||
// private function searchReference($rule, $bank_transaction)
|
||||
// {
|
||||
// if($bank_transaction->base_type == 'CREDIT'){
|
||||
// //search invoices
|
||||
// }
|
||||
// else{
|
||||
// //search expenses
|
||||
// }
|
||||
// }
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
public function vendor()
|
||||
{
|
||||
return $this->belongsTo(Vendor::class);
|
||||
}
|
||||
|
||||
public function client()
|
||||
{
|
||||
return $this->belongsTo(Client::class);
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function expense_cateogry()
|
||||
{
|
||||
return $this->belongsTo(ExpenseCategory::class)->withTrashed();
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ namespace App\Models;
|
||||
|
||||
use App\DataMapper\CompanySettings;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Language;
|
||||
use App\Models\Presenters\CompanyPresenter;
|
||||
use App\Models\PurchaseOrder;
|
||||
@ -123,6 +124,8 @@ class Company extends BaseModel
|
||||
'enabled_expense_tax_rates',
|
||||
'invoice_task_project',
|
||||
'report_include_deleted',
|
||||
'invoice_task_lock',
|
||||
'use_vendor_currency',
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
@ -188,6 +191,11 @@ class Company extends BaseModel
|
||||
return $this->hasMany(BankTransaction::class);
|
||||
}
|
||||
|
||||
public function bank_transaction_rules()
|
||||
{
|
||||
return $this->hasMany(BankTransactionRule::class);
|
||||
}
|
||||
|
||||
public function getCompanyIdAttribute()
|
||||
{
|
||||
return $this->encodePrimaryKey($this->id);
|
||||
@ -541,6 +549,23 @@ class Company extends BaseModel
|
||||
return $this->company_users()->withTrashed()->where('is_owner', true)->first()?->user;
|
||||
}
|
||||
|
||||
public function credit_rules()
|
||||
{
|
||||
return BankTransactionRule::query()
|
||||
->where('company_id', $this->id)
|
||||
->where('applies_to', 'CREDIT')
|
||||
->get();
|
||||
}
|
||||
|
||||
public function debit_rules()
|
||||
{
|
||||
return BankTransactionRule::query()
|
||||
->where('company_id', $this->id)
|
||||
->where('applies_to', 'DEBIT')
|
||||
->get();
|
||||
}
|
||||
|
||||
|
||||
public function resolveRouteBinding($value, $field = null)
|
||||
{
|
||||
return $this->where('id', $this->decodePrimaryKey($value))->firstOrFail();
|
||||
|
@ -79,7 +79,8 @@ class PurchaseOrder extends BaseModel
|
||||
'partial',
|
||||
'paid_to_date',
|
||||
'vendor_id',
|
||||
'last_viewed'
|
||||
'last_viewed',
|
||||
'currency_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -54,6 +54,10 @@ class Subscription extends BaseModel
|
||||
'price',
|
||||
'name',
|
||||
'currency_id',
|
||||
'registration_required',
|
||||
'optional_product_ids',
|
||||
'optional_recurring_product_ids',
|
||||
'use_inventory_management',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
|
@ -64,6 +64,7 @@ class VendorContact extends Authenticatable implements HasLocalePreference
|
||||
'email',
|
||||
'is_primary',
|
||||
'vendor_id',
|
||||
'send_email',
|
||||
];
|
||||
|
||||
public function avatar()
|
||||
|
@ -36,6 +36,7 @@ use Stripe\Exception\CardException;
|
||||
use Stripe\Exception\InvalidRequestException;
|
||||
use Stripe\Exception\RateLimitException;
|
||||
use Stripe\PaymentIntent;
|
||||
use App\Utils\Number;
|
||||
|
||||
class ACH
|
||||
{
|
||||
@ -172,9 +173,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
|
||||
@ -210,9 +211,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
if (substr($cgt->token, 0, 2) === 'pm') {
|
||||
@ -331,7 +332,7 @@ class ACH
|
||||
$data = [
|
||||
'gateway_type_id' => $cgt->gateway_type_id,
|
||||
'payment_type' => PaymentType::ACH,
|
||||
'transaction_reference' => $response->charges->data[0]->id,
|
||||
'transaction_reference' => isset($response->latest_charge) ? $response->latest_charge : $response->charges->data[0]->id,
|
||||
'amount' => $amount,
|
||||
];
|
||||
|
||||
@ -454,9 +455,9 @@ class ACH
|
||||
->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
if (substr($source->token, 0, 2) === 'pm') {
|
||||
|
@ -135,7 +135,7 @@ class BrowserPay implements MethodInterface
|
||||
'payment_method' => $gateway_response->payment_method,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($payment_method->card->brand)),
|
||||
'amount' => $this->stripe->convertFromStripeAmount($gateway_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => optional($payment_intent->charges->data[0])->id,
|
||||
'transaction_reference' => isset($payment_intent->latest_charge) ? $payment_intent->latest_charge : $payment_intent->charges->data[0]->id,
|
||||
'gateway_type_id' => GatewayType::APPLE_PAY,
|
||||
];
|
||||
|
||||
|
@ -32,6 +32,7 @@ use Stripe\Exception\CardException;
|
||||
use Stripe\Exception\InvalidRequestException;
|
||||
use Stripe\Exception\RateLimitException;
|
||||
use Stripe\StripeClient;
|
||||
use App\Utils\Number;
|
||||
|
||||
class Charge
|
||||
{
|
||||
@ -62,9 +63,9 @@ class Charge
|
||||
$invoice = Invoice::whereIn('id', $this->transformKeys(array_column($payment_hash->invoices(), 'invoice_id')))->withTrashed()->first();
|
||||
|
||||
if ($invoice) {
|
||||
$description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext', ['invoicenumber' => $invoice->number, 'amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
} else {
|
||||
$description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext_without_invoice', ['amount' => Number::formatMoney($amount, $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
}
|
||||
|
||||
$this->stripe->init();
|
||||
@ -141,20 +142,27 @@ class Charge
|
||||
$payment_method_type = PaymentType::SEPA;
|
||||
$status = Payment::STATUS_PENDING;
|
||||
} else {
|
||||
$payment_method_type = $response->charges->data[0]->payment_method_details->card->brand;
|
||||
|
||||
if(isset($response->latest_charge)) {
|
||||
$charge = \Stripe\Charge::retrieve($response->latest_charge, $this->stripe->stripe_connect_auth);
|
||||
$payment_method_type = $charge->payment_method_details->card->brand;
|
||||
}
|
||||
elseif(isset($response->charges->data[0]->payment_method_details->card->brand))
|
||||
$payment_method_type = $response->charges->data[0]->payment_method_details->card->brand;
|
||||
else
|
||||
$payment_method_type = 'visa';
|
||||
|
||||
$status = Payment::STATUS_COMPLETED;
|
||||
}
|
||||
|
||||
if($response?->status == 'processing'){
|
||||
//allows us to jump over the next stage - used for SEPA
|
||||
}elseif($response?->status != 'succeeded'){
|
||||
|
||||
if(!in_array($response?->status, ['succeeded', 'processing'])){
|
||||
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'gateway_type_id' => $cgt->gateway_type_id,
|
||||
'payment_type' => $this->transformPaymentTypeToConstant($payment_method_type),
|
||||
'transaction_reference' => $response->charges->data[0]->id,
|
||||
'transaction_reference' => isset($response->latest_charge) ? $response->latest_charge : $response->charges->data[0]->id,
|
||||
'amount' => $amount,
|
||||
];
|
||||
|
||||
@ -162,6 +170,7 @@ class Charge
|
||||
$payment->meta = $cgt->meta;
|
||||
$payment->save();
|
||||
|
||||
$payment_hash->data = array_merge((array) $payment_hash->data, ['payment_intent' => $response, 'amount_with_fee' => $amount]);
|
||||
$payment_hash->payment_id = $payment->id;
|
||||
$payment_hash->save();
|
||||
|
||||
|
@ -23,6 +23,7 @@ use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\PaymentDrivers\Stripe\Jobs\UpdateCustomer;
|
||||
use Stripe\PaymentIntent;
|
||||
use Stripe\PaymentMethod;
|
||||
use App\Utils\Number;
|
||||
|
||||
class CreditCard
|
||||
{
|
||||
@ -62,7 +63,7 @@ class CreditCard
|
||||
|
||||
// $description = $this->stripe->decodeUnicodeString(ctrans('texts.invoices') . ': ' . collect($data['invoices'])->pluck('invoice_number')) . " for client {$this->stripe->client->present()->name()}";
|
||||
$invoice_numbers = collect($data['invoices'])->pluck('invoice_number')->implode(',');
|
||||
$description = "Invoices: {$invoice_numbers} for {$data['total']['amount_with_fee']} for client {$this->stripe->client->present()->name()}";
|
||||
$description = ctrans('text.stripe_paymenttext', ['invoicenumber' => $invoice_numbers, 'amount' => Number::formatMoney($data['total']['amount_with_fee'], $this->stripe->client), 'client' => $this->stripe->client->present()->name()]);
|
||||
|
||||
$payment_intent_data = [
|
||||
'amount' => $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
@ -138,7 +139,7 @@ class CreditCard
|
||||
'payment_method' => $this->stripe->payment_hash->data->server_response->payment_method,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($stripe_method->card->brand)) ?: PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $this->stripe->convertFromStripeAmount($this->stripe->payment_hash->data->server_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()),
|
||||
'transaction_reference' => optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
|
||||
'transaction_reference' => isset($this->stripe->payment_hash->data->payment_intent->latest_charge) ? $this->stripe->payment_hash->data->payment_intent->latest_charge : optional($this->stripe->payment_hash->data->payment_intent->charges->data[0])->id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
|
@ -26,6 +26,7 @@ use App\PaymentDrivers\StripePaymentDriver;
|
||||
use App\Utils\Ninja;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Stripe\Customer;
|
||||
use Stripe\PaymentMethod;
|
||||
|
||||
@ -37,6 +38,8 @@ class ImportCustomers
|
||||
/** @var StripePaymentDriver */
|
||||
public $stripe;
|
||||
|
||||
private bool $completed = true;
|
||||
|
||||
public $update_payment_methods;
|
||||
|
||||
public function __construct(StripePaymentDriver $stripe)
|
||||
@ -63,7 +66,16 @@ class ImportCustomers
|
||||
$this->addCustomer($customer);
|
||||
}
|
||||
|
||||
$starting_after = end($customers->data)['id'];
|
||||
//handle
|
||||
// if(is_array($customers->data) && end($customers->data) && array_key_exists('id', end($customers->data)))
|
||||
// $starting_after = end($customers->data)['id'];
|
||||
// else
|
||||
// break;
|
||||
|
||||
$starting_after = isset(end($customers->data)['id']) ? end($customers->data)['id'] : false;
|
||||
|
||||
if(!$starting_after)
|
||||
break;
|
||||
|
||||
} while ($customers->has_more);
|
||||
}
|
||||
@ -128,10 +140,30 @@ class ImportCustomers
|
||||
$client->name = $customer->name ? $customer->name : $customer->email;
|
||||
|
||||
if (! isset($client->number) || empty($client->number)) {
|
||||
$client->number = $this->getNextClientNumber($client);
|
||||
}
|
||||
|
||||
$client->save();
|
||||
$x = 1;
|
||||
|
||||
do {
|
||||
try {
|
||||
$client->number = $this->getNextClientNumber($client);
|
||||
$client->saveQuietly();
|
||||
|
||||
$this->completed = false;
|
||||
} catch (QueryException $e) {
|
||||
$x++;
|
||||
|
||||
if ($x > 10) {
|
||||
$this->completed = false;
|
||||
}
|
||||
}
|
||||
} while ($this->completed);
|
||||
|
||||
|
||||
|
||||
}
|
||||
else{
|
||||
$client->save();
|
||||
}
|
||||
|
||||
$contact = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||
$contact->client_id = $client->id;
|
||||
|
@ -92,75 +92,94 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
if($this->payment_completed)
|
||||
return;
|
||||
|
||||
$company_gateway = CompanyGateway::find($this->company_gateway_id);
|
||||
$stripe_driver = $company_gateway->driver()->init();
|
||||
|
||||
if(optional($this->stripe_request['object']['charges']['data'][0])['id']){
|
||||
$charge_id = false;
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where('transaction_reference', $this->stripe_request['object']['charges']['data'][0]['id'])
|
||||
->first();
|
||||
|
||||
//return early
|
||||
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
|
||||
nlog(" payment found and status correct - returning ");
|
||||
return;
|
||||
}
|
||||
elseif($payment){
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
if(isset($this->stripe_request['object']['charges']) && optional($this->stripe_request['object']['charges']['data'][0])['id'])
|
||||
$charge_id = $this->stripe_request['object']['charges']['data'][0]['id']; // API VERSION 2018
|
||||
elseif (isset($this->stripe_request['object']['latest_charge']))
|
||||
$charge_id = $this->stripe_request['object']['latest_charge']; // API VERSION 2022-11-15
|
||||
|
||||
|
||||
$hash = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
|
||||
if(!$payment_hash)
|
||||
return;
|
||||
|
||||
nlog("payment intent");
|
||||
nlog($this->stripe_request);
|
||||
|
||||
if(array_key_exists('allowed_source_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['allowed_source_types']))
|
||||
{
|
||||
nlog("hash found");
|
||||
|
||||
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client);
|
||||
}
|
||||
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('card', $this->stripe_request['object']['payment_method_types']))
|
||||
{
|
||||
nlog("hash found");
|
||||
|
||||
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client);
|
||||
}
|
||||
elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('us_bank_account', $this->stripe_request['object']['payment_method_types']))
|
||||
{
|
||||
nlog("hash found");
|
||||
|
||||
$hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash'];
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateAchPayment($payment_hash, $client);
|
||||
}
|
||||
if(!$charge_id){
|
||||
nlog("could not resolve charge");
|
||||
return;
|
||||
}
|
||||
|
||||
$pi = \Stripe\PaymentIntent::retrieve($this->stripe_request['object']['id'], $stripe_driver->stripe_connect_auth);
|
||||
|
||||
$charge = \Stripe\Charge::retrieve($charge_id, $stripe_driver->stripe_connect_auth);
|
||||
|
||||
if(!$charge)
|
||||
{
|
||||
nlog("no charge found");
|
||||
nlog($this->stripe_request);
|
||||
return;
|
||||
}
|
||||
|
||||
$company = Company::where('company_key', $this->company_key)->first();
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $company->id)
|
||||
->where('transaction_reference', $charge['id'])
|
||||
->first();
|
||||
|
||||
//return early
|
||||
if($payment && $payment->status_id == Payment::STATUS_COMPLETED){
|
||||
nlog(" payment found and status correct - returning ");
|
||||
return;
|
||||
}
|
||||
elseif($payment){
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
$payment->save();
|
||||
}
|
||||
|
||||
$hash = isset($charge['metadata']['payment_hash']) ? $charge['metadata']['payment_hash'] : false;
|
||||
|
||||
if(!$hash)
|
||||
return;
|
||||
|
||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||
|
||||
if(!$payment_hash)
|
||||
return;
|
||||
|
||||
$stripe_driver->client = $payment_hash->fee_invoice->client;
|
||||
|
||||
$meta = [
|
||||
'gateway_type_id' => $pi['metadata']['gateway_type_id'],
|
||||
'transaction_reference' => $charge['id'],
|
||||
'customer' => $charge['customer'],
|
||||
'payment_method' => $charge['payment_method'],
|
||||
'card_details' => isset($charge['payment_method_details']['card']['brand']) ? $charge['payment_method_details']['card']['brand'] : PaymentType::CREDIT_CARD_OTHER
|
||||
];
|
||||
|
||||
if(isset($pi['allowed_source_types']) && in_array('card', $pi['allowed_source_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('card', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateCreditCardPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
elseif(isset($pi['payment_method_types']) && in_array('us_bank_account', $pi['payment_method_types']))
|
||||
{
|
||||
|
||||
$invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id);
|
||||
$client = $invoice->client;
|
||||
|
||||
$this->updateAchPayment($payment_hash, $client, $meta);
|
||||
}
|
||||
|
||||
SystemLogger::dispatch(
|
||||
['response' => $this->stripe_request, 'data' => []],
|
||||
@ -174,10 +193,10 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
|
||||
}
|
||||
|
||||
private function updateAchPayment($payment_hash, $client)
|
||||
private function updateAchPayment($payment_hash, $client, $meta)
|
||||
{
|
||||
$company_gateway = CompanyGateway::find($this->company_gateway_id);
|
||||
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id'];
|
||||
$payment_method_type = $meta['gateway_type_id'];
|
||||
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
|
||||
|
||||
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
|
||||
@ -188,7 +207,7 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
'payment_method' => $payment_hash->data->object->payment_method,
|
||||
'payment_type' => PaymentType::ACH,
|
||||
'amount' => $payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
|
||||
'transaction_reference' => $meta['transaction_reference'],
|
||||
'gateway_type_id' => GatewayType::BANK_TRANSFER,
|
||||
];
|
||||
|
||||
@ -205,9 +224,9 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
|
||||
try {
|
||||
|
||||
$customer = $driver->getCustomer($this->stripe_request['object']['charges']['data'][0]['customer']);
|
||||
$method = $driver->getStripePaymentMethod($this->stripe_request['object']['charges']['data'][0]['payment_method']);
|
||||
$payment_method = $this->stripe_request['object']['charges']['data'][0]['payment_method'];
|
||||
$customer = $driver->getCustomer($meta['customer']);
|
||||
$method = $driver->getStripePaymentMethod($meta['payment_method']);
|
||||
$payment_method = $meta['payment_method'];
|
||||
|
||||
$token_exists = ClientGatewayToken::where([
|
||||
'gateway_customer_reference' => $customer->id,
|
||||
@ -248,11 +267,44 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// private function updateSepaPayment($payment_hash, $client, $meta)
|
||||
// {
|
||||
|
||||
private function updateCreditCardPayment($payment_hash, $client)
|
||||
// $company_gateway = CompanyGateway::find($this->company_gateway_id);
|
||||
// $payment_method_type = GatewayType::SEPA;
|
||||
// $driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
|
||||
|
||||
// $payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
|
||||
// $payment_hash->save();
|
||||
// $driver->setPaymentHash($payment_hash);
|
||||
|
||||
// $data = [
|
||||
// 'payment_method' => $payment_hash->data->object->payment_method,
|
||||
// 'payment_type' => PaymentType::parseCardType(strtolower($meta['card_details'])) ?: PaymentType::CREDIT_CARD_OTHER,
|
||||
// 'amount' => $payment_hash->data->amount_with_fee,
|
||||
// 'transaction_reference' => $meta['transaction_reference'],
|
||||
// 'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
// ];
|
||||
|
||||
// $payment = $driver->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
|
||||
// SystemLogger::dispatch(
|
||||
// ['response' => $this->stripe_request, 'data' => $data],
|
||||
// SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
// SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
// SystemLog::TYPE_STRIPE,
|
||||
// $client,
|
||||
// $client->company,
|
||||
// );
|
||||
|
||||
|
||||
// }
|
||||
|
||||
|
||||
private function updateCreditCardPayment($payment_hash, $client, $meta)
|
||||
{
|
||||
$company_gateway = CompanyGateway::find($this->company_gateway_id);
|
||||
$payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id'];
|
||||
$payment_method_type = $meta['gateway_type_id'];
|
||||
$driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type);
|
||||
|
||||
$payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request);
|
||||
@ -261,9 +313,9 @@ class PaymentIntentWebhook implements ShouldQueue
|
||||
|
||||
$data = [
|
||||
'payment_method' => $payment_hash->data->object->payment_method,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower(optional($this->stripe_request['object']['charges']['data'][0]['payment_method_details']['card'])['brand'])) ?: PaymentType::CREDIT_CARD_OTHER,
|
||||
'payment_type' => PaymentType::parseCardType(strtolower($meta['card_details'])) ?: PaymentType::CREDIT_CARD_OTHER,
|
||||
'amount' => $payment_hash->data->amount_with_fee,
|
||||
'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'],
|
||||
'transaction_reference' => $meta['transaction_reference'],
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
|
||||
|
@ -84,7 +84,7 @@ class SEPA
|
||||
$this->stripe->payment_hash->data = array_merge((array) $this->stripe->payment_hash->data, $request->all());
|
||||
$this->stripe->payment_hash->save();
|
||||
|
||||
if (property_exists($gateway_response, 'status') && ($gateway_response->status == 'processing' || $gateway_response->status === 'succeeded')) {
|
||||
if (property_exists($gateway_response, 'status') && ($gateway_response->status == 'processing' || $gateway_response->status == 'succeeded')) {
|
||||
if ($request->store_card) {
|
||||
$this->storePaymentMethod($gateway_response);
|
||||
}
|
||||
|
@ -116,11 +116,14 @@ class StripePaymentDriver extends BaseDriver
|
||||
throw new StripeConnectFailure('Stripe Connect has not been configured');
|
||||
}
|
||||
} else {
|
||||
|
||||
$this->stripe = new StripeClient(
|
||||
$this->company_gateway->getConfigField('apiKey')
|
||||
);
|
||||
|
||||
Stripe::setApiKey($this->company_gateway->getConfigField('apiKey'));
|
||||
// Stripe::setApiVersion('2022-11-15');
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -657,14 +660,22 @@ class StripePaymentDriver extends BaseDriver
|
||||
], $this->stripe_connect_auth);
|
||||
|
||||
if ($charge->captured) {
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $transaction['payment_intent'])
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
$query->where('transaction_reference', $transaction['payment_intent'])
|
||||
->orWhere('transaction_reference', $transaction['id']);
|
||||
})
|
||||
->first();
|
||||
|
||||
$payment = false;
|
||||
|
||||
if(isset($transaction['payment_intent']))
|
||||
{
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $transaction['payment_intent'])
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->first();
|
||||
}
|
||||
elseif(isset($transaction['id'])) {
|
||||
$payment = Payment::query()
|
||||
->where('transaction_reference', $transaction['id'])
|
||||
->where('company_id', $request->getCompany()->id)
|
||||
->first();
|
||||
}
|
||||
|
||||
if ($payment) {
|
||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||
|
31
app/Policies/BankTransactionRulePolicy.php
Normal file
31
app/Policies/BankTransactionRulePolicy.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* Class BankTransactionPolicy.
|
||||
*/
|
||||
class BankTransactionRulePolicy extends EntityPolicy
|
||||
{
|
||||
/**
|
||||
* Checks if the user has create permissions.
|
||||
*
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ use App\Models\Activity;
|
||||
use App\Models\Bank;
|
||||
use App\Models\BankIntegration;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
@ -45,6 +46,7 @@ use App\Models\Webhook;
|
||||
use App\Policies\ActivityPolicy;
|
||||
use App\Policies\BankIntegrationPolicy;
|
||||
use App\Policies\BankTransactionPolicy;
|
||||
use App\Policies\BankTransactionRulePolicy;
|
||||
use App\Policies\ClientPolicy;
|
||||
use App\Policies\CompanyGatewayPolicy;
|
||||
use App\Policies\CompanyPolicy;
|
||||
@ -86,6 +88,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Activity::class => ActivityPolicy::class,
|
||||
BankIntegration::class => BankIntegrationPolicy::class,
|
||||
BankTransaction::class => BankTransactionPolicy::class,
|
||||
BankTransactionRule::class => BankTransactionRulePolicy::class,
|
||||
Client::class => ClientPolicy::class,
|
||||
Company::class => CompanyPolicy::class,
|
||||
CompanyToken::class => CompanyTokenPolicy::class,
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Jobs\Bank\MatchBankTransactions;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
@ -28,17 +29,25 @@ class BankTransactionRepository extends BaseRepository
|
||||
$bank_transaction->bank_integration_id = $data['bank_integration_id'];
|
||||
|
||||
$bank_transaction->fill($data);
|
||||
|
||||
$bank_transaction->save();
|
||||
|
||||
if($bank_transaction->base_type == 'CREDIT' && $invoice = $bank_transaction->service()->matchInvoiceNumber())
|
||||
{
|
||||
$bank_transaction->invoice_ids = $invoice->hashed_id;
|
||||
$bank_transaction->status_id = BankTransaction::STATUS_MATCHED;
|
||||
$bank_transaction->save();
|
||||
}
|
||||
$bank_transaction->service()->processRules();
|
||||
|
||||
return $bank_transaction;
|
||||
return $bank_transaction->fresh();
|
||||
}
|
||||
|
||||
public function convert_matched($bank_transactions)
|
||||
{
|
||||
|
||||
$data['transactions'] = $bank_transactions->map(function ($bt){
|
||||
return ['id' => $bt->id, 'invoice_ids' => $bt->invoice_ids];
|
||||
|
||||
})->toArray();
|
||||
|
||||
$bts = (new MatchBankTransactions(auth()->user()->company()->id, auth()->user()->company()->db, $data))->handle();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
35
app/Repositories/BankTransactionRuleRepository.php
Normal file
35
app/Repositories/BankTransactionRuleRepository.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Repositories;
|
||||
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskStatus;
|
||||
|
||||
/**
|
||||
* Class for bank transaction rule repository.
|
||||
*/
|
||||
class BankTransactionRuleRepository extends BaseRepository
|
||||
{
|
||||
|
||||
public function save($data, BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
|
||||
$bank_transaction_rule->fill($data);
|
||||
|
||||
$bank_transaction_rule->save();
|
||||
|
||||
return $bank_transaction_rule;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -11,74 +11,48 @@
|
||||
|
||||
namespace App\Services\Bank;
|
||||
|
||||
use App\Factory\ExpenseCategoryFactory;
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Company;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Bank\BankService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class BankMatchingService implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
private $company_id;
|
||||
public function __construct(protected int $company_id, private string $db){}
|
||||
|
||||
private Company $company;
|
||||
|
||||
private $db;
|
||||
|
||||
private $invoices;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public function __construct($company_id, $db)
|
||||
{
|
||||
$this->company_id = $company_id;
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
public function handle() :void
|
||||
{
|
||||
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
$this->company = Company::find($this->company_id);
|
||||
BankTransaction::where('company_id', $this->company_id)
|
||||
->where('status_id', BankTransaction::STATUS_UNMATCHED)
|
||||
->cursor()
|
||||
->each(function ($bt){
|
||||
|
||||
(new BankService($bt))->processRules();
|
||||
|
||||
$this->invoices = Invoice::where('company_id', $this->company->id)
|
||||
->whereIn('status_id', [1,2,3])
|
||||
->where('is_deleted', 0)
|
||||
->get();
|
||||
|
||||
$this->match();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private function match()
|
||||
public function middleware()
|
||||
{
|
||||
|
||||
BankTransaction::where('company_id', $this->company->id)
|
||||
->where('status_id', BankTransaction::STATUS_UNMATCHED)
|
||||
->cursor()
|
||||
->each(function ($bt){
|
||||
|
||||
$invoice = $this->invoices->first(function ($value, $key) use ($bt){
|
||||
|
||||
return str_contains($bt->description, $value->number);
|
||||
|
||||
});
|
||||
|
||||
if($invoice)
|
||||
{
|
||||
$bt->invoice_ids = $invoice->hashed_id;
|
||||
$bt->status_id = BankTransaction::STATUS_MATCHED;
|
||||
$bt->save();
|
||||
}
|
||||
|
||||
});
|
||||
return [new WithoutOverlapping("bank_match_rate:{$this->company_id}")];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace App\Services\Bank;
|
||||
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\Bank\ProcessBankRule;
|
||||
use App\Services\Bank\ProcessBankRules;
|
||||
|
||||
class BankService
|
||||
{
|
||||
@ -40,11 +40,9 @@ class BankService
|
||||
|
||||
}
|
||||
|
||||
public function processRule($rule)
|
||||
public function processRules()
|
||||
{
|
||||
(new ProcessBankRule($this->bank_transaction, $rule))->run();
|
||||
|
||||
return $this;
|
||||
(new ProcessBankRules($this->bank_transaction))->run();
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Bank;
|
||||
|
||||
use App\Models\BankTransaction;
|
||||
use App\Services\AbstractService;
|
||||
|
||||
class ProcessBankRule extends AbstractService
|
||||
{
|
||||
|
||||
public function __construct(private BankTransaction $bank_transaction, $rule){}
|
||||
|
||||
public function run() : void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
206
app/Services/Bank/ProcessBankRules.php
Normal file
206
app/Services/Bank/ProcessBankRules.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Services\Bank;
|
||||
|
||||
use App\Factory\ExpenseCategoryFactory;
|
||||
use App\Factory\ExpenseFactory;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\ExpenseCategory;
|
||||
use App\Models\Invoice;
|
||||
use App\Services\AbstractService;
|
||||
use App\Utils\Traits\GeneratesCounter;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class ProcessBankRules extends AbstractService
|
||||
{
|
||||
use GeneratesCounter;
|
||||
|
||||
protected $credit_rules;
|
||||
|
||||
protected $debit_rules;
|
||||
|
||||
protected $categories;
|
||||
|
||||
public function __construct(public BankTransaction $bank_transaction){}
|
||||
|
||||
public function run()
|
||||
{
|
||||
if($this->bank_transaction->base_type == 'DEBIT')
|
||||
$this->matchDebit();
|
||||
else
|
||||
$this->matchCredit();
|
||||
}
|
||||
|
||||
private function matchCredit()
|
||||
{
|
||||
|
||||
$this->credit_rules = $this->bank_transaction->company->credit_rules();
|
||||
|
||||
$this->invoices = Invoice::where('company_id', $this->bank_transaction->company_id)
|
||||
->whereIn('status_id', [1,2,3])
|
||||
->where('is_deleted', 0)
|
||||
->get();
|
||||
|
||||
$invoice = $this->invoices->first(function ($value, $key){
|
||||
|
||||
return str_contains($this->bank_transaction->description, $value->number);
|
||||
|
||||
});
|
||||
|
||||
if($invoice)
|
||||
{
|
||||
$this->bank_transaction->invoice_ids = $invoice->hashed_id;
|
||||
$this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED;
|
||||
$this->bank_transaction->save();
|
||||
return;
|
||||
}
|
||||
|
||||
//stub for credit rules
|
||||
foreach($this->credit_rules as $rule)
|
||||
{
|
||||
// $this->bank_transaction->bank_transaction_rule_id = $bank_transaction_rule->id;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function matchDebit()
|
||||
{
|
||||
|
||||
$this->debit_rules = $this->bank_transaction->company->debit_rules();
|
||||
|
||||
$this->categories = collect(Cache::get('bank_categories'));
|
||||
|
||||
foreach($this->debit_rules as $bank_transaction_rule)
|
||||
{
|
||||
|
||||
$matches = 0;
|
||||
|
||||
foreach($bank_transaction_rule['rules'] as $rule)
|
||||
{
|
||||
$rule_count = count($bank_transaction_rule['rules']);
|
||||
|
||||
if($rule['search_key'] == 'description')
|
||||
{
|
||||
|
||||
if($this->matchStringOperator($this->bank_transaction->description, $rule['value'], $rule['operator'])){
|
||||
$matches++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if($rule['search_key'] == 'amount')
|
||||
{
|
||||
|
||||
if($this->matchNumberOperator($this->bank_transaction->amount, $rule['value'] , $rule['operator'])){
|
||||
$matches++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(($bank_transaction_rule['matches_on_all'] && ($matches == $rule_count)) || (!$bank_transaction_rule['matches_on_all'] && $matches > 0))
|
||||
{
|
||||
|
||||
// $this->bank_transaction->client_id = empty($rule['client_id']) ? null : $rule['client_id'];
|
||||
$this->bank_transaction->vendor_id = $bank_transaction_rule->vendor_id;
|
||||
$this->bank_transaction->ninja_category_id = $bank_transaction_rule->category_id;
|
||||
$this->bank_transaction->status_id = BankTransaction::STATUS_MATCHED;
|
||||
$this->bank_transaction->bank_transaction_rule_id = $bank_transaction_rule->id;
|
||||
$this->bank_transaction->save();
|
||||
|
||||
if($bank_transaction_rule['auto_convert'])
|
||||
{
|
||||
|
||||
$expense = ExpenseFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id);
|
||||
$expense->category_id = $bank_transaction_rule->category_id ?: $this->resolveCategory();
|
||||
$expense->amount = $this->bank_transaction->amount;
|
||||
$expense->number = $this->getNextExpenseNumber($expense);
|
||||
$expense->currency_id = $this->bank_transaction->currency_id;
|
||||
$expense->date = Carbon::parse($this->bank_transaction->date);
|
||||
$expense->payment_date = Carbon::parse($this->bank_transaction->date);
|
||||
$expense->transaction_reference = $this->bank_transaction->description;
|
||||
$expense->transaction_id = $this->bank_transaction->id;
|
||||
$expense->vendor_id = $bank_transaction_rule->vendor_id;
|
||||
$expense->invoice_documents = $this->bank_transaction->company->invoice_expense_documents;
|
||||
$expense->should_be_invoiced = $this->bank_transaction->company->mark_expenses_invoiceable;
|
||||
$expense->save();
|
||||
|
||||
$this->bank_transaction->expense_id = $expense->id;
|
||||
$this->bank_transaction->status_id = BankTransaction::STATUS_CONVERTED;
|
||||
$this->bank_transaction->save();
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function resolveCategory()
|
||||
{
|
||||
$category = $this->categories->firstWhere('highLevelCategoryId', $this->bank_transaction->category_id);
|
||||
|
||||
$ec = ExpenseCategory::where('company_id', $this->bank_transaction->company_id)->where('bank_category_id', $this->bank_transaction->category_id)->first();
|
||||
|
||||
if($ec)
|
||||
return $ec->id;
|
||||
|
||||
if($category)
|
||||
{
|
||||
$ec = ExpenseCategoryFactory::create($this->bank_transaction->company_id, $this->bank_transaction->user_id);
|
||||
$ec->bank_category_id = $this->bank_transaction->category_id;
|
||||
$ec->name = $category->highLevelCategoryName;
|
||||
$ec->save();
|
||||
|
||||
return $ec->id;
|
||||
}
|
||||
}
|
||||
|
||||
private function matchNumberOperator($bt_value, $rule_value, $operator) :bool
|
||||
{
|
||||
|
||||
return match ($operator) {
|
||||
'>' => floatval($bt_value) > floatval($rule_value),
|
||||
'>=' => floatval($bt_value) >= floatval($rule_value),
|
||||
'=' => floatval($bt_value) == floatval($rule_value),
|
||||
'<' => floatval($bt_value) < floatval($rule_value),
|
||||
'<=' => floatval($bt_value) <= floatval($rule_value),
|
||||
default => false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private function matchStringOperator($bt_value, $rule_value, $operator) :bool
|
||||
{
|
||||
$bt_value = strtolower(str_replace(" ", "", $bt_value));
|
||||
$rule_value = strtolower(str_replace(" ", "", $rule_value));
|
||||
$rule_length = iconv_strlen($rule_value);
|
||||
|
||||
return match ($operator) {
|
||||
'is' => $bt_value == $rule_value,
|
||||
'contains' => stripos($bt_value, $rule_value) !== false,
|
||||
'starts_with' => substr($bt_value, 0, $rule_length) == $rule_value,
|
||||
'is_empty' => empty($bt_value),
|
||||
default => false,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -29,32 +29,42 @@ class ClientService
|
||||
|
||||
public function updateBalance(float $amount)
|
||||
{
|
||||
// $this->client->balance += $amount;
|
||||
|
||||
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
try {
|
||||
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $amount;
|
||||
$this->client->save();
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $amount;
|
||||
$this->client->save();
|
||||
|
||||
}, 2);
|
||||
}, 2);
|
||||
}
|
||||
catch (\Throwable $throwable) {
|
||||
nlog("DB ERROR " . $throwable->getMessage());
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
public function updateBalanceAndPaidToDate(float $balance, float $paid_to_date)
|
||||
{
|
||||
// $this->client->balance += $amount;
|
||||
// $this->client->paid_to_date += $amount;
|
||||
|
||||
\DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) {
|
||||
try {
|
||||
\DB::connection(config('database.default'))->transaction(function () use($balance, $paid_to_date) {
|
||||
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $balance;
|
||||
$this->client->paid_to_date += $paid_to_date;
|
||||
$this->client->save();
|
||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||
$this->client->balance += $balance;
|
||||
$this->client->paid_to_date += $paid_to_date;
|
||||
$this->client->save();
|
||||
|
||||
}, 2);
|
||||
}
|
||||
catch (\Throwable $throwable) {
|
||||
nlog("DB ERROR " . $throwable->getMessage());
|
||||
}
|
||||
|
||||
|
||||
}, 2);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -147,7 +147,7 @@ class ApplyPayment
|
||||
event(new InvoiceWasUpdated($this->invoice, $this->invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
|
||||
if ((int) $this->invoice->balance == 0) {
|
||||
$this->invoice->service()->deletePdf();
|
||||
$this->invoice->service()->touchPdf();
|
||||
$this->invoice = $this->invoice->fresh();
|
||||
event(new InvoiceWasPaid($this->invoice, $this->payment, $this->payment->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class SendEmail
|
||||
|
||||
$this->credit->invitations->each(function ($invitation) {
|
||||
if (! $invitation->contact->trashed() && $invitation->contact->email) {
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template);
|
||||
EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template)->delay(2);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -112,8 +112,7 @@ class AddGatewayFee extends AbstractService
|
||||
$this->invoice
|
||||
->client
|
||||
->service()
|
||||
->updateBalance($adjustment)
|
||||
->save();
|
||||
->updateBalance($adjustment);
|
||||
|
||||
$this->invoice
|
||||
->ledger()
|
||||
|
@ -103,7 +103,7 @@ class ApplyPayment extends AbstractService
|
||||
}
|
||||
});
|
||||
|
||||
$this->invoice->service()->applyNumber()->workFlow()->save();
|
||||
$this->invoice->service()->applyNumber()->workFlow()->touchPdf()->save();
|
||||
|
||||
$transaction = [
|
||||
'invoice' => $this->invoice->transaction_event(),
|
||||
|
@ -83,7 +83,7 @@ class ApplyPaymentAmount extends AbstractService
|
||||
->updatePaidToDate($payment->amount)
|
||||
->setCalculatedStatus()
|
||||
->applyNumber()
|
||||
->deletePdf()
|
||||
->touchPdf()
|
||||
->save();
|
||||
|
||||
$this->invoice
|
||||
|
@ -112,10 +112,12 @@ class InvoiceService
|
||||
* @param Payment $payment The Payment
|
||||
* @param float $payment_amount The Payment amount
|
||||
* @return InvoiceService Parent class object
|
||||
* @deprecated 24-11-2022 - cannot find any references to this method anywhere
|
||||
*/
|
||||
public function applyPayment(Payment $payment, float $payment_amount)
|
||||
{
|
||||
$this->deletePdf();
|
||||
// $this->deletePdf();
|
||||
$this->invoice = $this->markSent()->save();
|
||||
|
||||
$this->invoice = (new ApplyPayment($this->invoice, $payment, $payment_amount))->run();
|
||||
|
||||
@ -218,7 +220,6 @@ class InvoiceService
|
||||
public function markDeleted()
|
||||
{
|
||||
$this->removeUnpaidGatewayFees();
|
||||
$this->deletePdf();
|
||||
|
||||
$this->invoice = (new MarkInvoiceDeleted($this->invoice))->run();
|
||||
|
||||
@ -378,6 +379,7 @@ class InvoiceService
|
||||
})->toArray();
|
||||
|
||||
$this->invoice = $this->invoice->calc()->getInvoice();
|
||||
$this->invoice->service()->touchPdf();
|
||||
|
||||
/* 24-03-2022 */
|
||||
$new_balance = $this->invoice->balance;
|
||||
|
@ -36,7 +36,7 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -52,7 +52,7 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -68,7 +68,7 @@ class LedgerService
|
||||
|
||||
$this->entity->company_ledger()->save($company_ledger);
|
||||
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300));
|
||||
ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300)));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ class SendEmail
|
||||
$contact = $this->payment->client->contacts()->first();
|
||||
|
||||
if ($contact?->email)
|
||||
EmailPayment::dispatch($this->payment, $this->payment->company, $contact);
|
||||
EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(3));
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -785,7 +785,7 @@ class SubscriptionService
|
||||
*/
|
||||
public function triggerWebhook($context)
|
||||
{
|
||||
nlog("trigger webook");
|
||||
nlog("trigger webhook");
|
||||
|
||||
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) {
|
||||
return ["message" => "Success", "status_code" => 200];
|
||||
@ -901,6 +901,8 @@ class SubscriptionService
|
||||
*/
|
||||
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
||||
{
|
||||
$invoice_start_date = false;
|
||||
$refund_end_date = false;
|
||||
|
||||
//only refund if they are in the refund window.
|
||||
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
||||
@ -909,8 +911,11 @@ class SubscriptionService
|
||||
->orderBy('id', 'desc')
|
||||
->first();
|
||||
|
||||
$invoice_start_date = Carbon::parse($outstanding_invoice->date);
|
||||
$refund_end_date = $invoice_start_date->addSeconds($this->subscription->refund_period);
|
||||
if($outstanding_invoice)
|
||||
{
|
||||
$invoice_start_date = Carbon::parse($outstanding_invoice->date);
|
||||
$refund_end_date = $invoice_start_date->addSeconds($this->subscription->refund_period);
|
||||
}
|
||||
|
||||
/* Stop the recurring invoice and archive */
|
||||
$recurring_invoice->service()->stop()->save();
|
||||
@ -918,7 +923,7 @@ class SubscriptionService
|
||||
$recurring_invoice_repo->archive($recurring_invoice);
|
||||
|
||||
/* Refund only if we are in the window - and there is nothing outstanding on the invoice */
|
||||
if($refund_end_date->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
||||
if($refund_end_date && $refund_end_date->greaterThan(now()) && (int)$outstanding_invoice->balance == 0)
|
||||
{
|
||||
|
||||
if($outstanding_invoice->payments()->exists())
|
||||
|
90
app/Transformers/BankTransactionRuleTransformer.php
Normal file
90
app/Transformers/BankTransactionRuleTransformer.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\Transformers;
|
||||
|
||||
use App\Models\Account;
|
||||
use App\Models\BankTransaction;
|
||||
use App\Models\BankTransactionRule;
|
||||
use App\Models\Company;
|
||||
use App\Models\Expense;
|
||||
use App\Models\Invoice;
|
||||
use App\Transformers\VendorTransformer;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
|
||||
/**
|
||||
* Class BankTransactionRuleTransformer.
|
||||
*/
|
||||
class BankTransactionRuleTransformer extends EntityTransformer
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultIncludes = [
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $availableIncludes = [
|
||||
'company',
|
||||
'vendor',
|
||||
'client',
|
||||
'expense_category',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param BankTransaction $bank_integration
|
||||
* @return array
|
||||
*/
|
||||
public function transform(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
return [
|
||||
'id' => (string) $this->encodePrimaryKey($bank_transaction_rule->id),
|
||||
'name' => (string) $bank_transaction_rule->name,
|
||||
'rules' => $bank_transaction_rule->rules ?: (array) [],
|
||||
'auto_convert' => (bool) $bank_transaction_rule->auto_convert,
|
||||
'matches_on_all' => (bool) $bank_transaction_rule->matches_on_all,
|
||||
'applies_to' => (string) $bank_transaction_rule->applies_to,
|
||||
'client_id' => $this->encodePrimaryKey($bank_transaction_rule->client_id) ?: '',
|
||||
'vendor_id' => $this->encodePrimaryKey($bank_transaction_rule->vendor_id) ?: '',
|
||||
'category_id' => $this->encodePrimaryKey($bank_transaction_rule->category_id) ?: '',
|
||||
'is_deleted' => (bool) $bank_transaction_rule->is_deleted,
|
||||
'created_at' => (int) $bank_transaction_rule->created_at,
|
||||
'updated_at' => (int) $bank_transaction_rule->updated_at,
|
||||
'archived_at' => (int) $bank_transaction_rule->deleted_at,
|
||||
];
|
||||
}
|
||||
|
||||
public function includeCompany(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
$transformer = new CompanyTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->company, $transformer, Company::class);
|
||||
}
|
||||
|
||||
public function includeClient(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
$transformer = new ClientTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->expense, $transformer, Client::class);
|
||||
}
|
||||
|
||||
public function includeVendor(BankTransactionRule $bank_transaction_rule)
|
||||
{
|
||||
$transformer = new VendorTransformer($this->serializer);
|
||||
|
||||
return $this->includeItem($bank_transaction_rule->vendor, $transformer, Vendor::class);
|
||||
}
|
||||
|
||||
}
|
@ -67,6 +67,7 @@ class BankTransactionTransformer extends EntityTransformer
|
||||
'invoice_ids' => (string) $bank_transaction->invoice_ids ?: '',
|
||||
'expense_id'=> (string) $this->encodePrimaryKey($bank_transaction->expense_id) ?: '',
|
||||
'vendor_id'=> (string) $this->encodePrimaryKey($bank_transaction->vendor_id) ?: '',
|
||||
'bank_transaction_rule_id' => (string) $this->encodePrimaryKey($bank_transaction->bank_transaction_rule_id) ?: '',
|
||||
'is_deleted' => (bool) $bank_transaction->is_deleted,
|
||||
'created_at' => (int) $bank_transaction->created_at,
|
||||
'updated_at' => (int) $bank_transaction->updated_at,
|
||||
|
@ -43,6 +43,7 @@ use App\Models\TaxRate;
|
||||
use App\Models\User;
|
||||
use App\Models\Webhook;
|
||||
use App\Transformers\BankIntegrationTransformer;
|
||||
use App\Transformers\BankTransactionRuleTransformer;
|
||||
use App\Transformers\BankTransactionTransformer;
|
||||
use App\Transformers\PurchaseOrderTransformer;
|
||||
use App\Transformers\RecurringExpenseTransformer;
|
||||
@ -104,6 +105,7 @@ class CompanyTransformer extends EntityTransformer
|
||||
'purchase_orders',
|
||||
'bank_integrations',
|
||||
'bank_transactions',
|
||||
'bank_transaction_rules',
|
||||
];
|
||||
|
||||
/**
|
||||
@ -186,6 +188,8 @@ class CompanyTransformer extends EntityTransformer
|
||||
'enabled_expense_tax_rates' => (int) $company->enabled_expense_tax_rates,
|
||||
'invoice_task_project' => (bool) $company->invoice_task_project,
|
||||
'report_include_deleted' => (bool) $company->report_include_deleted,
|
||||
'invoice_task_lock' => (bool) $company->invoice_task_lock,
|
||||
'use_vendor_currency' => (bool) $company->use_vendor_currency,
|
||||
];
|
||||
}
|
||||
|
||||
@ -231,6 +235,14 @@ class CompanyTransformer extends EntityTransformer
|
||||
return $this->includeCollection($company->bank_transactions, $transformer, BankTransaction::class);
|
||||
}
|
||||
|
||||
|
||||
public function includeBankTransactionRules(Company $company)
|
||||
{
|
||||
$transformer = new BankTransactionRuleTransformer($this->serializer);
|
||||
|
||||
return $this->includeCollection($company->bank_transaction_rules, $transformer, BankTransactionRule::class);
|
||||
}
|
||||
|
||||
public function includeBankIntegrations(Company $company)
|
||||
{
|
||||
$transformer = new BankIntegrationTransformer($this->serializer);
|
||||
|
@ -132,6 +132,7 @@ class PurchaseOrderTransformer extends EntityTransformer
|
||||
'paid_to_date' => (float)$purchase_order->paid_to_date,
|
||||
'subscription_id' => $this->encodePrimaryKey($purchase_order->subscription_id),
|
||||
'expense_id' => $this->encodePrimaryKey($purchase_order->expense_id),
|
||||
'currency_id' => $purchase_order->currency_id ? (string) $purchase_order->currency_id : '',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,10 @@ class SubscriptionTransformer extends EntityTransformer
|
||||
'updated_at' => (int) $subscription->updated_at,
|
||||
'archived_at' => (int) $subscription->deleted_at,
|
||||
'plan_map' => '', //@deprecated 03/04/2021
|
||||
'use_inventory_management' => (bool) $subscription->use_inventory_management,
|
||||
'optional_recurring_product_ids' =>(string)$subscription->optional_recurring_product_ids,
|
||||
'optional_product_ids' => (string) $subscription->optional_product_ids,
|
||||
'registration_required' => (bool) $subscription->registration_required,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -72,7 +72,6 @@ class TaskTransformer extends EntityTransformer
|
||||
'user_id' => (string) $this->encodePrimaryKey($task->user_id),
|
||||
'assigned_user_id' => (string) $this->encodePrimaryKey($task->assigned_user_id),
|
||||
'number' => (string) $task->number ?: '',
|
||||
// 'start_time' => (int) $task->start_time,
|
||||
'description' => (string) $task->description ?: '',
|
||||
'duration' => (int) $task->duration ?: 0,
|
||||
'rate' => (float) $task->rate ?: 0,
|
||||
|
@ -68,9 +68,9 @@ trait Inviteable
|
||||
);
|
||||
$writer = new Writer($renderer);
|
||||
|
||||
$qr = $writer->writeString($this->getPaymentLink());
|
||||
$qr = $writer->writeString($this->getPaymentLink(), 'utf-8');
|
||||
|
||||
return "<svg viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
|
||||
return "<svg class='pqrcode' viewBox='0 0 200 200' width='200' height='200' x='0' y='0' xmlns='http://www.w3.org/2000/svg'>
|
||||
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
|
||||
|
||||
}
|
||||
@ -80,7 +80,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
$entity_type = Str::snake(class_basename($this->entityType()));
|
||||
@ -95,7 +95,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
@ -121,7 +121,7 @@ trait Inviteable
|
||||
if (Ninja::isHosted()) {
|
||||
$domain = $this->company->domain();
|
||||
} else {
|
||||
$domain = config('ninja.app_url');
|
||||
$domain = strlen($this->company->portal_domain) > 5 ? $this->company->portal_domain : config('ninja.app_url');
|
||||
}
|
||||
|
||||
switch ($this->company->portal_mode) {
|
||||
|
@ -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.42',
|
||||
'app_tag' => '5.5.42',
|
||||
'app_version' => '5.5.43',
|
||||
'app_tag' => '5.5.43',
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', ''),
|
||||
@ -211,4 +211,4 @@ return [
|
||||
'dev_mode' => env("YODLEE_DEV_MODE", false),
|
||||
'config_name' => env("YODLEE_CONFIG_NAME", false),
|
||||
],
|
||||
];
|
||||
];
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user