mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 19:34:39 -04:00
commit
c122cfaa28
@ -1 +1 @@
|
|||||||
5.5.42
|
5.5.43
|
@ -41,6 +41,7 @@ use App\Models\Vendor;
|
|||||||
use App\Models\VendorContact;
|
use App\Models\VendorContact;
|
||||||
use App\Repositories\InvoiceRepository;
|
use App\Repositories\InvoiceRepository;
|
||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
|
use App\Utils\Traits\AppSetup;
|
||||||
use App\Utils\Traits\GeneratesCounter;
|
use App\Utils\Traits\GeneratesCounter;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
@ -53,7 +54,7 @@ use Illuminate\Support\Str;
|
|||||||
|
|
||||||
class DemoMode extends Command
|
class DemoMode extends Command
|
||||||
{
|
{
|
||||||
use MakesHash, GeneratesCounter;
|
use MakesHash, GeneratesCounter, AppSetup;
|
||||||
|
|
||||||
protected $signature = 'ninja:demo-mode';
|
protected $signature = 'ninja:demo-mode';
|
||||||
|
|
||||||
@ -83,34 +84,14 @@ class DemoMode extends Command
|
|||||||
|
|
||||||
$cached_tables = config('ninja.cached_tables');
|
$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');
|
$this->info('Migrating');
|
||||||
Artisan::call('migrate:fresh --force');
|
Artisan::call('migrate:fresh --force');
|
||||||
|
|
||||||
$this->info('Seeding');
|
$this->info('Seeding');
|
||||||
Artisan::call('db:seed --force');
|
Artisan::call('db:seed --force');
|
||||||
|
|
||||||
|
$this->buildCache(true);
|
||||||
|
|
||||||
$this->info('Seeding Random Data');
|
$this->info('Seeding Random Data');
|
||||||
$this->createSmallAccount();
|
$this->createSmallAccount();
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ use App\Utils\Traits\MakesReminders;
|
|||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
|
|
||||||
|
//@deprecated 27-11-2022 - only ever should be used for testing
|
||||||
class SendRemindersCron extends Command
|
class SendRemindersCron extends Command
|
||||||
{
|
{
|
||||||
use MakesReminders, MakesDates;
|
use MakesReminders, MakesDates;
|
||||||
|
@ -48,46 +48,46 @@ class Kernel extends ConsoleKernel
|
|||||||
$schedule->job(new VersionCheck)->daily();
|
$schedule->job(new VersionCheck)->daily();
|
||||||
|
|
||||||
/* Checks and cleans redundant files */
|
/* Checks and cleans redundant files */
|
||||||
$schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping();
|
$schedule->job(new DiskCleanup)->dailyAt('02:10')->withoutOverlapping()->name('disk-cleanup-job')->onOneServer();
|
||||||
|
|
||||||
/* Send reminders */
|
/* 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 */
|
/* 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();
|
$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();
|
$schedule->job(new SubscriptionCron)->dailyAt('00:01')->withoutOverlapping()->name('subscription-job')->onOneServer();
|
||||||
|
|
||||||
/* Sends recurring invoices*/
|
/* Sends recurring invoices*/
|
||||||
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping();
|
$schedule->job(new RecurringInvoicesCron)->hourly()->withoutOverlapping()->name('recurring-invoice-job')->onOneServer();
|
||||||
|
|
||||||
/* Sends recurring invoices*/
|
/* 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 */
|
/* Fires notifications for expired Quotes */
|
||||||
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping();
|
$schedule->job(new QuoteCheckExpired)->dailyAt('05:10')->withoutOverlapping()->name('quote-expired-job')->onOneServer();
|
||||||
|
|
||||||
/* Performs auto billing */
|
/* Performs auto billing */
|
||||||
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping();
|
$schedule->job(new AutoBillCron)->dailyAt('06:20')->withoutOverlapping()->name('auto-bill-job')->onOneServer();
|
||||||
|
|
||||||
/* Checks the status of the scheduler */
|
/* Checks the status of the scheduler */
|
||||||
$schedule->job(new SchedulerCheck)->dailyAt('01:10')->withoutOverlapping();
|
$schedule->job(new SchedulerCheck)->dailyAt('01:10')->withoutOverlapping();
|
||||||
|
|
||||||
/* Checks for scheduled tasks */
|
/* Checks for scheduled tasks */
|
||||||
$schedule->job(new TaskScheduler())->dailyAt('06:50')->withoutOverlapping();
|
$schedule->job(new TaskScheduler())->dailyAt('06:50')->withoutOverlapping()->name('task-scheduler-job')->onOneServer();
|
||||||
|
|
||||||
/* Performs system maintenance such as pruning the backup table */
|
/* 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 */
|
/* Pulls in bank transactions from third party services */
|
||||||
$schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping();
|
$schedule->job(new BankTransactionSync)->dailyAt('04:10')->withoutOverlapping()->name('bank-trans-sync-job')->onOneServer();
|
||||||
|
|
||||||
if (Ninja::isSelfHost()) {
|
if (Ninja::isSelfHost()) {
|
||||||
|
|
||||||
@ -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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ class InvoiceFilters extends QueryFilters
|
|||||||
* @param string sort formatted as column|asc
|
* @param string sort formatted as column|asc
|
||||||
* @return Builder
|
* @return Builder
|
||||||
*/
|
*/
|
||||||
public function sort(string $sort) : Builder
|
public function sort(string $sort = '') : Builder
|
||||||
{
|
{
|
||||||
$sort_col = explode('|', $sort);
|
$sort_col = explode('|', $sort);
|
||||||
|
|
||||||
|
@ -539,7 +539,7 @@ class BaseController extends Controller
|
|||||||
$query->where('bank_integrations.user_id', $user->id);
|
$query->where('bank_integrations.user_id', $user->id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'company.bank_transaction_rules'=> function ($query) use ($updated_at, $user) {
|
'company.bank_transaction_rules'=> function ($query) use ($user) {
|
||||||
|
|
||||||
if (! $user->isAdmin()) {
|
if (! $user->isAdmin()) {
|
||||||
$query->where('bank_transaction_rules.user_id', $user->id);
|
$query->where('bank_transaction_rules.user_id', $user->id);
|
||||||
|
@ -428,13 +428,13 @@ class InvoiceController extends BaseController
|
|||||||
|
|
||||||
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
event(new InvoiceWasUpdated($invoice, $invoice->company, Ninja::eventVars(auth()->user() ? auth()->user()->id : null)));
|
||||||
|
|
||||||
$transaction = [
|
// $transaction = [
|
||||||
'invoice' => $invoice->transaction_event(),
|
// 'invoice' => $invoice->transaction_event(),
|
||||||
'payment' => [],
|
// 'payment' => [],
|
||||||
'client' => $invoice->client->transaction_event(),
|
// 'client' => $invoice->client->transaction_event(),
|
||||||
'credit' => [],
|
// 'credit' => [],
|
||||||
'metadata' => [],
|
// 'metadata' => [],
|
||||||
];
|
// ];
|
||||||
|
|
||||||
// TransactionLog::dispatch(TransactionEvent::INVOICE_UPDATED, $transaction, $invoice->company->db);
|
// TransactionLog::dispatch(TransactionEvent::INVOICE_UPDATED, $transaction, $invoice->company->db);
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class StoreBankIntegrationRequest extends Request
|
|||||||
{
|
{
|
||||||
$input = $this->all();
|
$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'];
|
$input['provider_name'] = $input['bank_account_name'];
|
||||||
|
|
||||||
$this->replace($input);
|
$this->replace($input);
|
||||||
|
@ -15,6 +15,7 @@ use App\Http\Requests\Request;
|
|||||||
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule;
|
||||||
use App\Models\Gateway;
|
use App\Models\Gateway;
|
||||||
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
|
||||||
class StoreCompanyGatewayRequest extends Request
|
class StoreCompanyGatewayRequest extends Request
|
||||||
{
|
{
|
||||||
@ -33,7 +34,7 @@ class StoreCompanyGatewayRequest extends Request
|
|||||||
public function rules()
|
public function rules()
|
||||||
{
|
{
|
||||||
$rules = [
|
$rules = [
|
||||||
'gateway_key' => 'required|alpha_num',
|
'gateway_key' => ['required','alpha_num',Rule::exists('gateways','key')],
|
||||||
'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
|
'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@ class GenericReportRequest extends Request
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'date_range' => 'bail|required|string',
|
'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',
|
'end_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||||
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
|
'start_date' => 'bail|required_if:date_range,custom|nullable|date',
|
||||||
'report_keys' => 'present|array',
|
'report_keys' => 'present|array',
|
||||||
@ -57,6 +55,12 @@ class GenericReportRequest extends Request
|
|||||||
$input['send_email'] = true;
|
$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);
|
$this->replace($input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class ConfirmSmsRequest extends Request
|
|||||||
*/
|
*/
|
||||||
public function authorize() : bool
|
public function authorize() : bool
|
||||||
{
|
{
|
||||||
return auth()->user()->isAdmin();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rules()
|
public function rules()
|
||||||
|
@ -24,7 +24,7 @@ class GenerateSmsRequest extends Request
|
|||||||
*/
|
*/
|
||||||
public function authorize() : bool
|
public function authorize() : bool
|
||||||
{
|
{
|
||||||
return auth()->user();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -36,6 +36,8 @@ use Illuminate\Queue\SerializesModels;
|
|||||||
use Illuminate\Support\Facades\App;
|
use Illuminate\Support\Facades\App;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
use function Amp\call;
|
||||||
|
|
||||||
class CompanyExport implements ShouldQueue
|
class CompanyExport implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, MakesHash;
|
||||||
@ -531,6 +533,15 @@ class CompanyExport implements ShouldQueue
|
|||||||
|
|
||||||
$path = 'backups';
|
$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_path = public_path('storage/backups/'.$file_name);
|
||||||
$zip = new \ZipArchive();
|
$zip = new \ZipArchive();
|
||||||
|
|
||||||
|
@ -62,8 +62,15 @@ class AutoBillCron
|
|||||||
|
|
||||||
nlog($auto_bill_partial_invoices->count().' partial invoices to auto bill');
|
nlog($auto_bill_partial_invoices->count().' partial invoices to auto bill');
|
||||||
|
|
||||||
$auto_bill_partial_invoices->cursor()->each(function ($invoice) {
|
$auto_bill_partial_invoices->chunk(100, function ($invoices) {
|
||||||
AutoBill::dispatch($invoice->id, false);
|
|
||||||
|
foreach($invoices as $invoice)
|
||||||
|
{
|
||||||
|
AutoBill::dispatch($invoice->id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
||||||
@ -79,8 +86,15 @@ class AutoBillCron
|
|||||||
|
|
||||||
nlog($auto_bill_invoices->count().' full invoices to auto bill');
|
nlog($auto_bill_invoices->count().' full invoices to auto bill');
|
||||||
|
|
||||||
$auto_bill_invoices->cursor()->each(function ($invoice) {
|
$auto_bill_invoices->chunk(100, function ($invoices) {
|
||||||
AutoBill::dispatch($invoice->id, false);
|
|
||||||
|
foreach($invoices as $invoice)
|
||||||
|
{
|
||||||
|
AutoBill::dispatch($invoice->id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//multiDB environment, need to
|
//multiDB environment, need to
|
||||||
@ -100,8 +114,14 @@ class AutoBillCron
|
|||||||
|
|
||||||
nlog($auto_bill_partial_invoices->count()." partial invoices to auto bill db = {$db}");
|
nlog($auto_bill_partial_invoices->count()." partial invoices to auto bill db = {$db}");
|
||||||
|
|
||||||
$auto_bill_partial_invoices->cursor()->each(function ($invoice) use ($db) {
|
$auto_bill_partial_invoices->chunk(100, function ($invoices) use($db){
|
||||||
AutoBill::dispatch($invoice->id, $db);
|
|
||||||
|
foreach($invoices as $invoice)
|
||||||
|
{
|
||||||
|
AutoBill::dispatch($invoice->id, $db);
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
$auto_bill_invoices = Invoice::whereDate('due_date', '<=', now())
|
$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}");
|
nlog($auto_bill_invoices->count()." full invoices to auto bill db = {$db}");
|
||||||
|
|
||||||
$auto_bill_invoices->cursor()->each(function ($invoice) use ($db) {
|
$auto_bill_invoices->chunk(100, function ($invoices) use($db){
|
||||||
nlog($this->counter);
|
|
||||||
AutoBill::dispatch($invoice->id, $db);
|
foreach($invoices as $invoice)
|
||||||
$this->counter++;
|
{
|
||||||
|
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'));
|
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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -99,11 +99,11 @@ class EmailEntity implements ShouldQueue
|
|||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle() :void
|
||||||
{
|
{
|
||||||
/* Don't fire emails if the company is disabled */
|
/* Don't fire emails if the company is disabled */
|
||||||
if ($this->company->is_disabled) {
|
if ($this->company->is_disabled) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set DB */
|
/* Set DB */
|
||||||
|
@ -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();
|
||||||
|
@ -49,10 +49,6 @@ class SystemMaintenance implements ShouldQueue
|
|||||||
|
|
||||||
nlog('Starting System Maintenance');
|
nlog('Starting System Maintenance');
|
||||||
|
|
||||||
if (Ninja::isHosted()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$delete_pdf_days = config('ninja.maintenance.delete_pdfs');
|
$delete_pdf_days = config('ninja.maintenance.delete_pdfs');
|
||||||
|
|
||||||
nlog("Number of days to keep PDFs {$delete_pdf_days}");
|
nlog("Number of days to keep PDFs {$delete_pdf_days}");
|
||||||
|
@ -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) {
|
|
||||||
|
|
||||||
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()
|
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,124 @@ class ReminderJob implements ShouldQueue
|
|||||||
*
|
*
|
||||||
* @return void
|
* @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();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//24-11-2022 fix for potential memory leak during a long running process, the second reminder may run twice
|
|
||||||
foreach (config('ninja.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);
|
set_time_limit(0);
|
||||||
|
|
||||||
Invoice::query()
|
if (! config('ninja.db.multi_db_enabled'))
|
||||||
->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) {
|
|
||||||
if ($invoice->isPayable()) {
|
|
||||||
|
|
||||||
//Attempts to prevent duplicates from sending
|
nlog("Sending invoice reminders on ".now()->format('Y-m-d h:i:s'));
|
||||||
if($invoice->reminder_last_sent && Carbon::parse($invoice->reminder_last_sent)->startOfDay()->eq(now()->startOfDay())){
|
|
||||||
nlog("caught a duplicate reminder for invoice {$invoice->number}");
|
Invoice::query()
|
||||||
return;
|
->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) {
|
||||||
|
|
||||||
|
foreach($invoices as $invoice)
|
||||||
|
{
|
||||||
|
$this->sendReminderForInvoice($invoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
$reminder_template = $invoice->calculateTemplate('invoice');
|
sleep(2);
|
||||||
nlog("reminder template = {$reminder_template}");
|
|
||||||
$invoice = $this->calcLateFee($invoice, $reminder_template);
|
|
||||||
$invoice->service()->touchReminder($reminder_template)->save();
|
|
||||||
$invoice->service()->touchPdf(true);
|
|
||||||
|
|
||||||
//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) {
|
} else {
|
||||||
EmailEntity::dispatch($invitation, $invitation->company, $reminder_template);
|
//multiDB environment, need to
|
||||||
nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}");
|
|
||||||
});
|
foreach (MultiDB::$dbs as $db)
|
||||||
|
{
|
||||||
|
|
||||||
if ($invoice->invitations->count() > 0) {
|
MultiDB::setDB($db);
|
||||||
event(new InvoiceWasEmailed($invoice->invitations->first(), $invoice->company, Ninja::eventVars(), $reminder_template));
|
|
||||||
}
|
nlog("Sending invoice reminders on db {$db} ".now()->format('Y-m-d h:i:s'));
|
||||||
}
|
|
||||||
$invoice->service()->setReminder()->save();
|
Invoice::query()
|
||||||
} else {
|
->where('is_deleted', 0)
|
||||||
$invoice->next_send_date = null;
|
->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL])
|
||||||
$invoice->save();
|
->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();
|
||||||
|
}
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -220,16 +252,18 @@ class ReminderJob implements ShouldQueue
|
|||||||
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance);
|
$invoice->client->service()->updateBalance($invoice->balance - $temp_invoice_balance);
|
||||||
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
$invoice->ledger()->updateInvoiceBalance($invoice->balance - $temp_invoice_balance, "Late Fee Adjustment for invoice {$invoice->number}");
|
||||||
|
|
||||||
$transaction = [
|
// $transaction = [
|
||||||
'invoice' => $invoice->transaction_event(),
|
// 'invoice' => $invoice->transaction_event(),
|
||||||
'payment' => [],
|
// 'payment' => [],
|
||||||
'client' => $invoice->client->transaction_event(),
|
// 'client' => $invoice->client->transaction_event(),
|
||||||
'credit' => [],
|
// 'credit' => [],
|
||||||
'metadata' => ['setLateFee'],
|
// 'metadata' => ['setLateFee'],
|
||||||
];
|
// ];
|
||||||
|
|
||||||
// TransactionLog::dispatch(TransactionEvent::CLIENT_STATUS, $transaction, $invoice->company->db);
|
// TransactionLog::dispatch(TransactionEvent::CLIENT_STATUS, $transaction, $invoice->company->db);
|
||||||
|
|
||||||
|
$invoice->service()->touchPdf(true);
|
||||||
|
|
||||||
return $invoice;
|
return $invoice;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
$client = new Client();
|
||||||
|
$response = $client->get($cc_endpoint);
|
||||||
|
|
||||||
$currency_api = json_decode($response->getBody());
|
$currency_api = json_decode($response->getBody());
|
||||||
|
|
||||||
/* Update all currencies */
|
/* Update all currencies */
|
||||||
Currency::all()->each(function ($currency) use ($currency_api) {
|
Currency::all()->each(function ($currency) use ($currency_api) {
|
||||||
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
$currency->exchange_rate = $currency_api->rates->{$currency->code};
|
||||||
$currency->save();
|
$currency->save();
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Rebuild the cache */
|
/* Rebuild the cache */
|
||||||
$currencies = Currency::orderBy('name')->get();
|
$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')));
|
$version_file = trim(@file_get_contents(config('ninja.version_url')));
|
||||||
|
|
||||||
nlog("latest version = {$version_file}");
|
|
||||||
|
|
||||||
if (Ninja::isSelfHost() && $version_file) {
|
if (Ninja::isSelfHost() && $version_file) {
|
||||||
Account::whereNotNull('id')->update(['latest_version' => $version_file]);
|
Account::whereNotNull('id')->update(['latest_version' => $version_file]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ninja::isSelfHost()) {
|
if (Ninja::isSelfHost()) {
|
||||||
|
|
||||||
|
nlog("latest version = {$version_file}");
|
||||||
|
|
||||||
$account = Account::first();
|
$account = Account::first();
|
||||||
|
|
||||||
if (! $account) {
|
if (! $account) {
|
||||||
|
@ -29,8 +29,6 @@ class InvoiceFailedEmailNotification
|
|||||||
|
|
||||||
use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $delay = 10;
|
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -48,7 +46,7 @@ class InvoiceFailedEmailNotification
|
|||||||
$first_notification_sent = true;
|
$first_notification_sent = true;
|
||||||
|
|
||||||
$invoice = $event->invitation->invoice;
|
$invoice = $event->invitation->invoice;
|
||||||
$invoice->update(['last_sent_date' => now()]);
|
// $invoice->update(['last_sent_date' => now()]);
|
||||||
|
|
||||||
$nmo = new NinjaMailerObject;
|
$nmo = new NinjaMailerObject;
|
||||||
$nmo->mailable = new NinjaMailer((new EntityFailedSendObject($event->invitation, 'invoice', $event->template, $event->message))->build());
|
$nmo->mailable = new NinjaMailer((new EntityFailedSendObject($event->invitation, 'invoice', $event->template, $event->message))->build());
|
||||||
|
@ -331,7 +331,7 @@ class ACH
|
|||||||
$data = [
|
$data = [
|
||||||
'gateway_type_id' => $cgt->gateway_type_id,
|
'gateway_type_id' => $cgt->gateway_type_id,
|
||||||
'payment_type' => PaymentType::ACH,
|
'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,
|
'amount' => $amount,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@ class BrowserPay implements MethodInterface
|
|||||||
'payment_method' => $gateway_response->payment_method,
|
'payment_method' => $gateway_response->payment_method,
|
||||||
'payment_type' => PaymentType::parseCardType(strtolower($payment_method->card->brand)),
|
'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()),
|
'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,
|
'gateway_type_id' => GatewayType::APPLE_PAY,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -141,20 +141,27 @@ class Charge
|
|||||||
$payment_method_type = PaymentType::SEPA;
|
$payment_method_type = PaymentType::SEPA;
|
||||||
$status = Payment::STATUS_PENDING;
|
$status = Payment::STATUS_PENDING;
|
||||||
} else {
|
} 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;
|
$status = Payment::STATUS_COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($response?->status == 'processing'){
|
if(!in_array($response?->status, ['succeeded', 'processing'])){
|
||||||
//allows us to jump over the next stage - used for SEPA
|
|
||||||
}elseif($response?->status != 'succeeded'){
|
|
||||||
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
|
$this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400));
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'gateway_type_id' => $cgt->gateway_type_id,
|
'gateway_type_id' => $cgt->gateway_type_id,
|
||||||
'payment_type' => $this->transformPaymentTypeToConstant($payment_method_type),
|
'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,
|
'amount' => $amount,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -162,6 +169,7 @@ class Charge
|
|||||||
$payment->meta = $cgt->meta;
|
$payment->meta = $cgt->meta;
|
||||||
$payment->save();
|
$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->payment_id = $payment->id;
|
||||||
$payment_hash->save();
|
$payment_hash->save();
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ use App\PaymentDrivers\StripePaymentDriver;
|
|||||||
use App\Utils\Ninja;
|
use App\Utils\Ninja;
|
||||||
use App\Utils\Traits\GeneratesCounter;
|
use App\Utils\Traits\GeneratesCounter;
|
||||||
use App\Utils\Traits\MakesHash;
|
use App\Utils\Traits\MakesHash;
|
||||||
|
use Illuminate\Database\QueryException;
|
||||||
use Stripe\Customer;
|
use Stripe\Customer;
|
||||||
use Stripe\PaymentMethod;
|
use Stripe\PaymentMethod;
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ class ImportCustomers
|
|||||||
/** @var StripePaymentDriver */
|
/** @var StripePaymentDriver */
|
||||||
public $stripe;
|
public $stripe;
|
||||||
|
|
||||||
|
private bool $completed = true;
|
||||||
|
|
||||||
public $update_payment_methods;
|
public $update_payment_methods;
|
||||||
|
|
||||||
public function __construct(StripePaymentDriver $stripe)
|
public function __construct(StripePaymentDriver $stripe)
|
||||||
@ -64,9 +67,14 @@ class ImportCustomers
|
|||||||
}
|
}
|
||||||
|
|
||||||
//handle
|
//handle
|
||||||
if(is_array($customers->data) && end($customers->data) && array_key_exists('id', end($customers->data)))
|
// if(is_array($customers->data) && end($customers->data) && array_key_exists('id', end($customers->data)))
|
||||||
$starting_after = end($customers->data)['id'];
|
// $starting_after = end($customers->data)['id'];
|
||||||
else
|
// else
|
||||||
|
// break;
|
||||||
|
|
||||||
|
$starting_after = isset(end($customers->data)['id']) ? end($customers->data)['id'] : false;
|
||||||
|
|
||||||
|
if(!$starting_after)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} while ($customers->has_more);
|
} while ($customers->has_more);
|
||||||
@ -132,10 +140,30 @@ class ImportCustomers
|
|||||||
$client->name = $customer->name ? $customer->name : $customer->email;
|
$client->name = $customer->name ? $customer->name : $customer->email;
|
||||||
|
|
||||||
if (! isset($client->number) || empty($client->number)) {
|
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 = ClientContactFactory::create($client->company_id, $client->user_id);
|
||||||
$contact->client_id = $client->id;
|
$contact->client_id = $client->id;
|
||||||
|
@ -138,6 +138,9 @@ class PaymentIntentWebhook implements ShouldQueue
|
|||||||
|
|
||||||
$hash = isset($charge['metadata']['payment_hash']) ? $charge['metadata']['payment_hash'] : false;
|
$hash = isset($charge['metadata']['payment_hash']) ? $charge['metadata']['payment_hash'] : false;
|
||||||
|
|
||||||
|
if(!$hash)
|
||||||
|
return;
|
||||||
|
|
||||||
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
$payment_hash = PaymentHash::where('hash', $hash)->first();
|
||||||
|
|
||||||
if(!$payment_hash)
|
if(!$payment_hash)
|
||||||
@ -264,6 +267,39 @@ class PaymentIntentWebhook implements ShouldQueue
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// private function updateSepaPayment($payment_hash, $client, $meta)
|
||||||
|
// {
|
||||||
|
|
||||||
|
// $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)
|
private function updateCreditCardPayment($payment_hash, $client, $meta)
|
||||||
{
|
{
|
||||||
|
@ -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->data = array_merge((array) $this->stripe->payment_hash->data, $request->all());
|
||||||
$this->stripe->payment_hash->save();
|
$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) {
|
if ($request->store_card) {
|
||||||
$this->storePaymentMethod($gateway_response);
|
$this->storePaymentMethod($gateway_response);
|
||||||
}
|
}
|
||||||
|
@ -660,14 +660,22 @@ class StripePaymentDriver extends BaseDriver
|
|||||||
], $this->stripe_connect_auth);
|
], $this->stripe_connect_auth);
|
||||||
|
|
||||||
if ($charge->captured) {
|
if ($charge->captured) {
|
||||||
$payment = Payment::query()
|
|
||||||
->where('transaction_reference', $transaction['payment_intent'])
|
$payment = false;
|
||||||
->where('company_id', $request->getCompany()->id)
|
|
||||||
->where(function ($query) use ($transaction) {
|
if(isset($transaction['payment_intent']))
|
||||||
$query->where('transaction_reference', $transaction['payment_intent'])
|
{
|
||||||
->orWhere('transaction_reference', $transaction['id']);
|
$payment = Payment::query()
|
||||||
})
|
->where('transaction_reference', $transaction['payment_intent'])
|
||||||
->first();
|
->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) {
|
if ($payment) {
|
||||||
$payment->status_id = Payment::STATUS_COMPLETED;
|
$payment->status_id = Payment::STATUS_COMPLETED;
|
||||||
|
@ -31,32 +31,16 @@ use Illuminate\Support\Facades\Cache;
|
|||||||
|
|
||||||
class BankMatchingService implements ShouldQueue
|
class BankMatchingService implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, GeneratesCounter;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
private Company $company;
|
public function __construct(protected int $company_id, private string $db){}
|
||||||
|
|
||||||
private $invoices;
|
public function handle() :void
|
||||||
|
|
||||||
public $deleteWhenMissingModels = true;
|
|
||||||
|
|
||||||
public function __construct(private int $company_id, private string $db){}
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
MultiDB::setDb($this->db);
|
MultiDB::setDb($this->db);
|
||||||
|
|
||||||
$this->company = Company::find($this->company_id);
|
BankTransaction::where('company_id', $this->company_id)
|
||||||
|
|
||||||
$this->matchTransactions();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private function matchTransactions()
|
|
||||||
{
|
|
||||||
|
|
||||||
BankTransaction::where('company_id', $this->company->id)
|
|
||||||
->where('status_id', BankTransaction::STATUS_UNMATCHED)
|
->where('status_id', BankTransaction::STATUS_UNMATCHED)
|
||||||
->cursor()
|
->cursor()
|
||||||
->each(function ($bt){
|
->each(function ($bt){
|
||||||
@ -64,11 +48,11 @@ class BankMatchingService implements ShouldQueue
|
|||||||
(new BankService($bt))->processRules();
|
(new BankService($bt))->processRules();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function middleware()
|
public function middleware()
|
||||||
{
|
{
|
||||||
return [new WithoutOverlapping($this->company_id)];
|
return [new WithoutOverlapping("bank_match_rate:{$this->company_id}")];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,6 @@ class ClientService
|
|||||||
try {
|
try {
|
||||||
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
\DB::connection(config('database.default'))->transaction(function () use($amount) {
|
||||||
|
|
||||||
nlog("inside transaction - updating balance by {$amount}");
|
|
||||||
|
|
||||||
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
$this->client = Client::withTrashed()->where('id', $this->client->id)->lockForUpdate()->first();
|
||||||
$this->client->balance += $amount;
|
$this->client->balance += $amount;
|
||||||
$this->client->save();
|
$this->client->save();
|
||||||
|
@ -45,7 +45,7 @@ class SendEmail
|
|||||||
|
|
||||||
$this->credit->invitations->each(function ($invitation) {
|
$this->credit->invitations->each(function ($invitation) {
|
||||||
if (! $invitation->contact->trashed() && $invitation->contact->email) {
|
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
|
$this->invoice
|
||||||
->client
|
->client
|
||||||
->service()
|
->service()
|
||||||
->updateBalance($adjustment)
|
->updateBalance($adjustment);
|
||||||
->save();
|
|
||||||
|
|
||||||
$this->invoice
|
$this->invoice
|
||||||
->ledger()
|
->ledger()
|
||||||
|
@ -36,7 +36,7 @@ class LedgerService
|
|||||||
|
|
||||||
$this->entity->company_ledger()->save($company_ledger);
|
$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;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -52,7 +52,7 @@ class LedgerService
|
|||||||
|
|
||||||
$this->entity->company_ledger()->save($company_ledger);
|
$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;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ class LedgerService
|
|||||||
|
|
||||||
$this->entity->company_ledger()->save($company_ledger);
|
$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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ class SendEmail
|
|||||||
$contact = $this->payment->client->contacts()->first();
|
$contact = $this->payment->client->contacts()->first();
|
||||||
|
|
||||||
if ($contact?->email)
|
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)
|
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())
|
||||||
|
@ -70,7 +70,7 @@ trait Inviteable
|
|||||||
|
|
||||||
$qr = $writer->writeString($this->getPaymentLink(), 'utf-8');
|
$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>";
|
<rect x='0' y='0' width='100%'' height='100%' />{$qr}</svg>";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,8 @@ return [
|
|||||||
'require_https' => env('REQUIRE_HTTPS', true),
|
'require_https' => env('REQUIRE_HTTPS', true),
|
||||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||||
'app_version' => '5.5.42',
|
'app_version' => '5.5.43',
|
||||||
'app_tag' => '5.5.42',
|
'app_tag' => '5.5.43',
|
||||||
'minimum_client_version' => '5.0.16',
|
'minimum_client_version' => '5.0.16',
|
||||||
'terms_version' => '1.0.1',
|
'terms_version' => '1.0.1',
|
||||||
'api_secret' => env('API_SECRET', ''),
|
'api_secret' => env('API_SECRET', ''),
|
||||||
@ -211,5 +211,4 @@ return [
|
|||||||
'dev_mode' => env("YODLEE_DEV_MODE", false),
|
'dev_mode' => env("YODLEE_DEV_MODE", false),
|
||||||
'config_name' => env("YODLEE_CONFIG_NAME", false),
|
'config_name' => env("YODLEE_CONFIG_NAME", false),
|
||||||
],
|
],
|
||||||
'dbs' => ['db-ninja-01','db-ninja-02']
|
];
|
||||||
];
|
|
@ -4,9 +4,12 @@ use App\Models\Currency;
|
|||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use App\Utils\Traits\AppSetup;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
|
use AppSetup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the migrations.
|
* Run the migrations.
|
||||||
*
|
*
|
||||||
@ -65,6 +68,9 @@ return new class extends Migration
|
|||||||
|
|
||||||
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
\Illuminate\Support\Facades\Artisan::call('ninja:design-update');
|
||||||
|
|
||||||
|
$this->buildCache(true);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
6
public/flutter_service_worker.js
vendored
6
public/flutter_service_worker.js
vendored
@ -296,12 +296,12 @@ const RESOURCES = {
|
|||||||
"canvaskit/profiling/canvaskit.wasm": "95a45378b69e77af5ed2bc72b2209b94",
|
"canvaskit/profiling/canvaskit.wasm": "95a45378b69e77af5ed2bc72b2209b94",
|
||||||
"canvaskit/canvaskit.js": "2bc454a691c631b07a9307ac4ca47797",
|
"canvaskit/canvaskit.js": "2bc454a691c631b07a9307ac4ca47797",
|
||||||
"canvaskit/canvaskit.wasm": "bf50631470eb967688cca13ee181af62",
|
"canvaskit/canvaskit.wasm": "bf50631470eb967688cca13ee181af62",
|
||||||
"/": "686443a10b66af2e91462a76c539a39a",
|
"/": "83aac01abae3eb27a43bdb1f55895304",
|
||||||
"version.json": "eaa34b1fc12792d815c0443fbfbc076b",
|
"version.json": "04dda2a311adc1947afc5c1055f19e16",
|
||||||
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
"manifest.json": "ef43d90e57aa7682d7e2cfba2f484a40",
|
||||||
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
"icons/Icon-512.png": "0f9aff01367f0a0c69773d25ca16ef35",
|
||||||
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
"icons/Icon-192.png": "bb1cf5f6982006952211c7c8404ffbed",
|
||||||
"main.dart.js": "301d099d604c2a0ce586552202f8d3ed"
|
"main.dart.js": "91d67096ddd71f22b9cae01c774205f2"
|
||||||
};
|
};
|
||||||
|
|
||||||
// The application shell files that are downloaded before a service worker can
|
// The application shell files that are downloaded before a service worker can
|
||||||
|
60975
public/main.dart.js
vendored
60975
public/main.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
60413
public/main.foss.dart.js
vendored
60413
public/main.foss.dart.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
117
public/main.profile.dart.js
vendored
117
public/main.profile.dart.js
vendored
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
|||||||
{"app_name":"invoiceninja_flutter","version":"5.0.100","build_number":"100","package_name":"invoiceninja_flutter"}
|
{"app_name":"invoiceninja_flutter","version":"5.0.102","build_number":"102","package_name":"invoiceninja_flutter"}
|
@ -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