mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Merge pull request #7987 from turbo124/v5-develop
Refactor the way we execute scheduled commands
This commit is contained in:
commit
faf4b312d0
@ -54,13 +54,13 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping()->name('reminder-job')->onOneServer();
|
$schedule->job(new ReminderJob)->hourly()->withoutOverlapping()->name('reminder-job')->onOneServer();
|
||||||
|
|
||||||
/* Returns the number of jobs in the queue */
|
/* 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 */
|
/* Checks for large companies and marked them as is_large */
|
||||||
$schedule->job(new CompanySizeCheck)->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
|
$schedule->job(new CompanySizeCheck)->dailyAt('23:20')->withoutOverlapping()->name('company-size-job')->onOneServer();
|
||||||
|
|
||||||
/* Pulls in the latest exchange rates */
|
/* 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 */
|
/* Runs cleanup code for subscriptions */
|
||||||
$schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping()->name('subscription-job')->onOneServer();
|
$schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping()->name('subscription-job')->onOneServer();
|
||||||
@ -105,11 +105,11 @@ class Kernel extends ConsoleKernel
|
|||||||
//not used @deprecate
|
//not used @deprecate
|
||||||
// $schedule->job(new SendFailedEmails)->daily()->withoutOverlapping();
|
// $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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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'))
|
if(array_key_exists('transaction.base_type', $transaction) && (($transaction['transaction.base_type'] == 'CREDIT') || strtolower($transaction['transaction.base_type']) == 'deposit'))
|
||||||
return 'CREDIT';
|
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';
|
return 'DEBIT';
|
||||||
|
|
||||||
if(array_key_exists('transaction.category_id', $transaction))
|
if(array_key_exists('transaction.category_id', $transaction))
|
||||||
|
@ -46,39 +46,62 @@ class RecurringExpensesCron
|
|||||||
nlog('Sending recurring expenses '.Carbon::now()->format('Y-m-d h:i:s'));
|
nlog('Sending recurring expenses '.Carbon::now()->format('Y-m-d h:i:s'));
|
||||||
|
|
||||||
if (! config('ninja.db.multi_db_enabled')) {
|
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 {
|
} else {
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
foreach (MultiDB::$dbs as $db) {
|
foreach (MultiDB::$dbs as $db) {
|
||||||
MultiDB::setDB($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()
|
private function getRecurringExpenses()
|
||||||
{
|
{
|
||||||
$recurring_expenses = RecurringExpense::where('next_send_date', '<=', now()->toDateTimeString())
|
//extracting this back to the if/else block to test duplicate crons
|
||||||
->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 generateExpense(RecurringExpense $recurring_expense)
|
private function generateExpense(RecurringExpense $recurring_expense)
|
||||||
|
@ -41,44 +41,62 @@ class SubscriptionCron
|
|||||||
nlog('Subscription Cron');
|
nlog('Subscription Cron');
|
||||||
|
|
||||||
if (! config('ninja.db.multi_db_enabled')) {
|
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 {
|
} else {
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
foreach (MultiDB::$dbs as $db) {
|
foreach (MultiDB::$dbs as $db) {
|
||||||
MultiDB::setDB($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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -44,38 +44,30 @@ class BankTransactionSync implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
// if (! Ninja::isHosted()) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
foreach (MultiDB::$dbs as $db) {
|
foreach (MultiDB::$dbs as $db)
|
||||||
|
{
|
||||||
MultiDB::setDB($db);
|
MultiDB::setDB($db);
|
||||||
|
|
||||||
nlog("syncing transactions");
|
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()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (! config('ninja.db.multi_db_enabled')) {
|
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 {
|
} else {
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
foreach (MultiDB::$dbs as $db) {
|
foreach (MultiDB::$dbs as $db) {
|
||||||
MultiDB::setDB($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
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle() :void
|
||||||
{
|
{
|
||||||
LightLogs::create(new QueueSizeAnalytic(Queue::size()))
|
LightLogs::create(new QueueSizeAnalytic(Queue::size()))
|
||||||
->send();
|
->send();
|
||||||
|
@ -42,39 +42,61 @@ class QuoteCheckExpired implements ShouldQueue
|
|||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
if (! config('ninja.db.multi_db_enabled'))
|
if (! config('ninja.db.multi_db_enabled')){
|
||||||
return $this->checkForExpiredQuotes();
|
|
||||||
|
|
||||||
foreach (MultiDB::$dbs as $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);
|
||||||
|
});
|
||||||
|
|
||||||
MultiDB::setDB($db);
|
}
|
||||||
|
else {
|
||||||
|
|
||||||
$this->checkForExpiredQuotes();
|
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()
|
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)
|
private function queueExpiredQuoteNotification(Quote $quote)
|
||||||
|
@ -44,92 +44,112 @@ class ReminderJob implements ShouldQueue
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle() :void
|
||||||
{
|
{
|
||||||
if (! config('ninja.db.multi_db_enabled')) {
|
if (! config('ninja.db.multi_db_enabled'))
|
||||||
$this->processReminders();
|
{
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
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')
|
||||||
|
->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')->cursor()->each(function ($invoice) {
|
||||||
|
|
||||||
|
$this->sendReminderForInvoice($invoice);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
/*
|
|
||||||
foreach (MultiDB::$dbs as $db) {
|
foreach (MultiDB::$dbs as $db)
|
||||||
|
{
|
||||||
|
|
||||||
MultiDB::setDB($db);
|
MultiDB::setDB($db);
|
||||||
nlog("set db {$db}");
|
|
||||||
$this->processReminders();
|
nlog("Sending invoice reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
|
||||||
}
|
|
||||||
*/
|
Invoice::query()
|
||||||
//24-11-2022 fix for potential memory leak during a long running process, the second reminder may run twice
|
->where('is_deleted', 0)
|
||||||
foreach (config('ninja.dbs') as $db) {
|
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||||
MultiDB::setDB($db);
|
->whereNull('deleted_at')
|
||||||
nlog("set db {$db}");
|
->where('balance', '>', 0)
|
||||||
$this->processReminders();
|
->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')->cursor()->each(function ($invoice) {
|
||||||
|
// if ($invoice->refresh() && $invoice->isPayable()) {
|
||||||
|
|
||||||
|
$this->sendReminderForInvoice($invoice);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processReminders()
|
private function sendReminderForInvoice($invoice) {
|
||||||
{
|
|
||||||
nlog('Sending invoice reminders '.now()->format('Y-m-d h:i:s'));
|
|
||||||
|
|
||||||
set_time_limit(0);
|
if ($invoice->isPayable()) {
|
||||||
|
|
||||||
Invoice::query()
|
//Attempts to prevent duplicates from sending
|
||||||
->where('is_deleted', 0)
|
if($invoice->reminder_last_sent && Carbon::parse($invoice->reminder_last_sent)->startOfDay()->eq(now()->startOfDay())){
|
||||||
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
nlog("caught a duplicate reminder for invoice {$invoice->number}");
|
||||||
->whereNull('deleted_at')
|
return;
|
||||||
->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')->cursor()->each(function ($invoice) {
|
|
||||||
// if ($invoice->refresh() && $invoice->isPayable()) {
|
|
||||||
if ($invoice->isPayable()) {
|
|
||||||
|
|
||||||
//Attempts to prevent duplicates from sending
|
$reminder_template = $invoice->calculateTemplate('invoice');
|
||||||
if($invoice->reminder_last_sent && Carbon::parse($invoice->reminder_last_sent)->startOfDay()->eq(now()->startOfDay())){
|
nlog("reminder template = {$reminder_template}");
|
||||||
nlog("caught a duplicate reminder for invoice {$invoice->number}");
|
$invoice->service()->touchReminder($reminder_template)->save();
|
||||||
return;
|
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
||||||
}
|
|
||||||
|
|
||||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
//20-04-2022 fixes for endless reminders - generic template naming was wrong
|
||||||
nlog("reminder template = {$reminder_template}");
|
$enabled_reminder = 'enable_'.$reminder_template;
|
||||||
$invoice->service()->touchReminder($reminder_template)->save();
|
if ($reminder_template == 'endless_reminder') {
|
||||||
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
$enabled_reminder = 'enable_reminder_endless';
|
||||||
|
}
|
||||||
|
|
||||||
//20-04-2022 fixes for endless reminders - generic template naming was wrong
|
//check if this reminder needs to be emailed
|
||||||
$enabled_reminder = 'enable_'.$reminder_template;
|
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
||||||
if ($reminder_template == 'endless_reminder') {
|
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) &&
|
||||||
$enabled_reminder = 'enable_reminder_endless';
|
$invoice->client->getSetting($enabled_reminder) &&
|
||||||
}
|
$invoice->client->getSetting('send_reminders') &&
|
||||||
|
(Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) {
|
||||||
|
|
||||||
//check if this reminder needs to be emailed
|
$invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) {
|
||||||
//15-01-2022 - insert addition if block if send_reminders is definitely set
|
EmailEntity::dispatchSync($invitation, $invitation->company, $reminder_template);
|
||||||
if (in_array($reminder_template, ['reminder1', 'reminder2', 'reminder3', 'reminder_endless', 'endless_reminder']) &&
|
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
|
||||||
$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) {
|
if ($invoice->invitations->count() > 0) {
|
||||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $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();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
$invoice->service()->setReminder()->save();
|
||||||
|
} else {
|
||||||
|
$invoice->next_send_date = null;
|
||||||
|
$invoice->save();
|
||||||
|
}
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,19 +34,7 @@ class UpdateExchangeRates implements ShouldQueue
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle() :void
|
||||||
{
|
|
||||||
if (config('ninja.db.multi_db_enabled')) {
|
|
||||||
foreach (MultiDB::$dbs as $db) {
|
|
||||||
MultiDB::setDB($db);
|
|
||||||
$this->updateCurrencies();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$this->updateCurrencies();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateCurrencies()
|
|
||||||
{
|
{
|
||||||
info('updating currencies');
|
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'));
|
$cc_endpoint = sprintf('https://openexchangerates.org/api/latest.json?app_id=%s', config('ninja.currency_converter_api_key'));
|
||||||
|
|
||||||
$client = new Client();
|
if (config('ninja.db.multi_db_enabled')) {
|
||||||
$response = $client->get($cc_endpoint);
|
foreach (MultiDB::$dbs as $db) {
|
||||||
|
MultiDB::setDB($db);
|
||||||
|
|
||||||
$currency_api = json_decode($response->getBody());
|
$client = new Client();
|
||||||
|
$response = $client->get($cc_endpoint);
|
||||||
|
|
||||||
/* Update all currencies */
|
$currency_api = json_decode($response->getBody());
|
||||||
Currency::all()->each(function ($currency) use ($currency_api) {
|
|
||||||
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
|
||||||
$currency->save();
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Rebuild the cache */
|
/* Update all currencies */
|
||||||
$currencies = Currency::orderBy('name')->get();
|
Currency::all()->each(function ($currency) use ($currency_api) {
|
||||||
|
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||||
|
$currency->save();
|
||||||
|
});
|
||||||
|
|
||||||
Cache::forever('currencies', $currencies);
|
/* Rebuild the cache */
|
||||||
|
$currencies = Currency::orderBy('name')->get();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -785,7 +785,7 @@ class SubscriptionService
|
|||||||
*/
|
*/
|
||||||
public function triggerWebhook($context)
|
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) {
|
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];
|
return ["message" => "Success", "status_code" => 200];
|
||||||
@ -901,6 +901,8 @@ class SubscriptionService
|
|||||||
*/
|
*/
|
||||||
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
public function handleCancellation(RecurringInvoice $recurring_invoice)
|
||||||
{
|
{
|
||||||
|
$invoice_start_date = false;
|
||||||
|
$refund_end_date = false;
|
||||||
|
|
||||||
//only refund if they are in the refund window.
|
//only refund if they are in the refund window.
|
||||||
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
$outstanding_invoice = Invoice::where('subscription_id', $this->subscription->id)
|
||||||
@ -909,8 +911,11 @@ class SubscriptionService
|
|||||||
->orderBy('id', 'desc')
|
->orderBy('id', 'desc')
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
$invoice_start_date = Carbon::parse($outstanding_invoice->date);
|
if($outstanding_invoice)
|
||||||
$refund_end_date = $invoice_start_date->addSeconds($this->subscription->refund_period);
|
{
|
||||||
|
$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 */
|
/* Stop the recurring invoice and archive */
|
||||||
$recurring_invoice->service()->stop()->save();
|
$recurring_invoice->service()->stop()->save();
|
||||||
@ -918,7 +923,7 @@ class SubscriptionService
|
|||||||
$recurring_invoice_repo->archive($recurring_invoice);
|
$recurring_invoice_repo->archive($recurring_invoice);
|
||||||
|
|
||||||
/* Refund only if we are in the window - and there is nothing outstanding on the 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())
|
if($outstanding_invoice->payments()->exists())
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
@include('portal.ninja2020.quotes.includes.actions', ['quote' => $quote])
|
@include('portal.ninja2020.quotes.includes.actions', ['quote' => $quote])
|
||||||
</div>
|
</div>
|
||||||
@elseif($quote->status_id === \App\Models\Quote::STATUS_CONVERTED)
|
@elseif($quote->status_id == \App\Models\Quote::STATUS_CONVERTED)
|
||||||
|
|
||||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
@ -57,7 +57,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@elseif($quote->status_id === \App\Models\Quote::STATUS_APPROVED)
|
@elseif($quote->status_id == \App\Models\Quote::STATUS_APPROVED)
|
||||||
|
|
||||||
<div class="bg-white shadow sm:rounded-lg mb-4">
|
<div class="bg-white shadow sm:rounded-lg mb-4">
|
||||||
<div class="px-4 py-5 sm:p-6">
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
@ -94,7 +94,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
<div class="mt-5 sm:mt-0 sm:ml-6 sm:flex-shrink-0 sm:flex sm:items-center">
|
||||||
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
<div class="inline-flex rounded-md shadow-sm" x-data="{ open: false }">
|
||||||
<button class="button button-danger" translate @click="open = true">Request Cancellation
|
<button class="button button-danger" translate @click="open = true">{{ ctrans('texts.request_cancellation') }}
|
||||||
</button>
|
</button>
|
||||||
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
@include('portal.ninja2020.recurring_invoices.includes.modals.cancellation')
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user