diff --git a/VERSION.txt b/VERSION.txt index 5c41e37e4728..b2198ea2611b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.42 \ No newline at end of file +5.5.43 \ No newline at end of file diff --git a/app/Console/Commands/DemoMode.php b/app/Console/Commands/DemoMode.php index 20324049db61..d7c8bf94b0d5 100644 --- a/app/Console/Commands/DemoMode.php +++ b/app/Console/Commands/DemoMode.php @@ -41,6 +41,7 @@ use App\Models\Vendor; use App\Models\VendorContact; use App\Repositories\InvoiceRepository; use App\Utils\Ninja; +use App\Utils\Traits\AppSetup; use App\Utils\Traits\GeneratesCounter; use App\Utils\Traits\MakesHash; use Carbon\Carbon; @@ -53,7 +54,7 @@ use Illuminate\Support\Str; class DemoMode extends Command { - use MakesHash, GeneratesCounter; + use MakesHash, GeneratesCounter, AppSetup; protected $signature = 'ninja:demo-mode'; @@ -83,34 +84,14 @@ class DemoMode extends Command $cached_tables = config('ninja.cached_tables'); - foreach ($cached_tables as $name => $class) { - if (! Cache::has($name)) { - // check that the table exists in case the migration is pending - if (! Schema::hasTable((new $class())->getTable())) { - continue; - } - if ($name == 'payment_terms') { - $orderBy = 'num_days'; - } elseif ($name == 'fonts') { - $orderBy = 'sort_order'; - } elseif (in_array($name, ['currencies', 'industries', 'languages', 'countries', 'banks'])) { - $orderBy = 'name'; - } else { - $orderBy = 'id'; - } - $tableData = $class::orderBy($orderBy)->get(); - if ($tableData->count()) { - Cache::forever($name, $tableData); - } - } - } - $this->info('Migrating'); Artisan::call('migrate:fresh --force'); $this->info('Seeding'); Artisan::call('db:seed --force'); + $this->buildCache(true); + $this->info('Seeding Random Data'); $this->createSmallAccount(); diff --git a/app/Filters/InvoiceFilters.php b/app/Filters/InvoiceFilters.php index c155d2061516..f599511ee13d 100644 --- a/app/Filters/InvoiceFilters.php +++ b/app/Filters/InvoiceFilters.php @@ -185,7 +185,7 @@ class InvoiceFilters extends QueryFilters * @param string sort formatted as column|asc * @return Builder */ - public function sort(string $sort) : Builder + public function sort(string $sort = '') : Builder { $sort_col = explode('|', $sort); diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 068168876a04..b5b0f249f2ea 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -539,7 +539,7 @@ class BaseController extends Controller $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()) { $query->where('bank_transaction_rules.user_id', $user->id); diff --git a/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php index b1889b6a5fd0..933f5848624d 100644 --- a/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php +++ b/app/Http/Requests/BankIntegration/StoreBankIntegrationRequest.php @@ -44,7 +44,7 @@ class StoreBankIntegrationRequest extends Request { $input = $this->all(); - if(!array_key_exists('provider_name', $input) || strlen($input['provider_name']) == 0) + if(!array_key_exists('provider_name', $input) || strlen($input['provider_name']) == 0 && array_key_exists('bank_account_name', $input)) $input['provider_name'] = $input['bank_account_name']; $this->replace($input); diff --git a/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php b/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php index cd013302c085..ea88548dfdcd 100644 --- a/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php +++ b/app/Http/Requests/CompanyGateway/StoreCompanyGatewayRequest.php @@ -15,6 +15,7 @@ use App\Http\Requests\Request; use App\Http\ValidationRules\ValidCompanyGatewayFeesAndLimitsRule; use App\Models\Gateway; use App\Utils\Traits\CompanyGatewayFeesAndLimitsSaver; +use Illuminate\Validation\Rule; class StoreCompanyGatewayRequest extends Request { @@ -33,7 +34,7 @@ class StoreCompanyGatewayRequest extends Request public function rules() { $rules = [ - 'gateway_key' => 'required|alpha_num', + 'gateway_key' => ['required','alpha_num',Rule::exists('gateways','key')], 'fees_and_limits' => new ValidCompanyGatewayFeesAndLimitsRule(), ]; diff --git a/app/Jobs/Cron/AutoBillCron.php b/app/Jobs/Cron/AutoBillCron.php index c291c5bca7ef..164b3d6943a5 100644 --- a/app/Jobs/Cron/AutoBillCron.php +++ b/app/Jobs/Cron/AutoBillCron.php @@ -62,8 +62,15 @@ class AutoBillCron nlog($auto_bill_partial_invoices->count().' partial invoices to auto bill'); - $auto_bill_partial_invoices->cursor()->each(function ($invoice) { - AutoBill::dispatch($invoice->id, false); + $auto_bill_partial_invoices->chunk(100, function ($invoices) { + + foreach($invoices as $invoice) + { + AutoBill::dispatch($invoice->id, false); + } + + sleep(2); + }); $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) @@ -79,8 +86,15 @@ class AutoBillCron nlog($auto_bill_invoices->count().' full invoices to auto bill'); - $auto_bill_invoices->cursor()->each(function ($invoice) { - AutoBill::dispatch($invoice->id, false); + $auto_bill_invoices->chunk(100, function ($invoices) { + + foreach($invoices as $invoice) + { + AutoBill::dispatch($invoice->id, false); + } + + sleep(2); + }); } else { //multiDB environment, need to @@ -100,8 +114,14 @@ class AutoBillCron nlog($auto_bill_partial_invoices->count()." partial invoices to auto bill db = {$db}"); - $auto_bill_partial_invoices->cursor()->each(function ($invoice) use ($db) { - AutoBill::dispatch($invoice->id, $db); + $auto_bill_partial_invoices->chunk(100, function ($invoices) use($db){ + + foreach($invoices as $invoice) + { + AutoBill::dispatch($invoice->id, $db); + } + + sleep(2); }); $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) @@ -117,10 +137,15 @@ class AutoBillCron nlog($auto_bill_invoices->count()." full invoices to auto bill db = {$db}"); - $auto_bill_invoices->cursor()->each(function ($invoice) use ($db) { - nlog($this->counter); - AutoBill::dispatch($invoice->id, $db); - $this->counter++; + $auto_bill_invoices->chunk(100, function ($invoices) use($db){ + + foreach($invoices as $invoice) + { + AutoBill::dispatch($invoice->id, $db); + } + + sleep(2); + }); } diff --git a/app/Jobs/Util/ReminderJob.php b/app/Jobs/Util/ReminderJob.php index f09dcb9b55f3..2ff606b4ce21 100644 --- a/app/Jobs/Util/ReminderJob.php +++ b/app/Jobs/Util/ReminderJob.php @@ -148,7 +148,7 @@ class ReminderJob implements ShouldQueue (Ninja::isSelfHost() || $invoice->company->account->isPaidHostedClient())) { $invoice->invitations->each(function ($invitation) use ($invoice, $reminder_template) { - EmailEntity::dispatch($invitation, $invitation->company, $reminder_template); + EmailEntity::dispatch($invitation, $invitation->company, $reminder_template)->delay(now()->addSeconds(3)); nlog("Firing reminder email for invoice {$invoice->number} - {$reminder_template}"); }); diff --git a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php index ac100e0e4646..a684b680cef3 100644 --- a/app/Listeners/Invoice/InvoiceFailedEmailNotification.php +++ b/app/Listeners/Invoice/InvoiceFailedEmailNotification.php @@ -29,8 +29,6 @@ class InvoiceFailedEmailNotification use UserNotifies, Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public $delay = 10; - public function __construct() { } diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index 2a5689ccf901..cc25056ca673 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -331,7 +331,7 @@ class ACH $data = [ 'gateway_type_id' => $cgt->gateway_type_id, 'payment_type' => PaymentType::ACH, - 'transaction_reference' => $response->charges->data[0]->id, + 'transaction_reference' => isset($response->latest_charge) ? $response->latest_charge : $response->charges->data[0]->id, 'amount' => $amount, ]; diff --git a/app/PaymentDrivers/Stripe/BrowserPay.php b/app/PaymentDrivers/Stripe/BrowserPay.php index 6840421f3e28..95fd39a8503d 100644 --- a/app/PaymentDrivers/Stripe/BrowserPay.php +++ b/app/PaymentDrivers/Stripe/BrowserPay.php @@ -135,7 +135,7 @@ class BrowserPay implements MethodInterface 'payment_method' => $gateway_response->payment_method, 'payment_type' => PaymentType::parseCardType(strtolower($payment_method->card->brand)), 'amount' => $this->stripe->convertFromStripeAmount($gateway_response->amount, $this->stripe->client->currency()->precision, $this->stripe->client->currency()), - 'transaction_reference' => optional($payment_intent->charges->data[0])->id, + 'transaction_reference' => isset($payment_intent->latest_charge) ? $payment_intent->latest_charge : $payment_intent->charges->data[0]->id, 'gateway_type_id' => GatewayType::APPLE_PAY, ]; diff --git a/app/PaymentDrivers/Stripe/Charge.php b/app/PaymentDrivers/Stripe/Charge.php index 691f6ca5bb13..7dbd60c07f57 100644 --- a/app/PaymentDrivers/Stripe/Charge.php +++ b/app/PaymentDrivers/Stripe/Charge.php @@ -141,20 +141,27 @@ class Charge $payment_method_type = PaymentType::SEPA; $status = Payment::STATUS_PENDING; } else { - $payment_method_type = $response->charges->data[0]->payment_method_details->card->brand; + + if(isset($response->latest_charge)) { + $charge = \Stripe\Charge::retrieve($response->latest_charge, $this->stripe->stripe_connect_auth); + $payment_method_type = $charge->payment_method_details->card->brand; + } + elseif(isset($response->charges->data[0]->payment_method_details->card->brand)) + $payment_method_type = $response->charges->data[0]->payment_method_details->card->brand; + else + $payment_method_type = 'visa'; + $status = Payment::STATUS_COMPLETED; } - - if($response?->status == 'processing'){ - //allows us to jump over the next stage - used for SEPA - }elseif($response?->status != 'succeeded'){ + + if(!in_array($response?->status, ['succeeded', 'processing'])){ $this->stripe->processInternallyFailedPayment($this->stripe, new \Exception('Auto billing failed.',400)); } $data = [ 'gateway_type_id' => $cgt->gateway_type_id, 'payment_type' => $this->transformPaymentTypeToConstant($payment_method_type), - 'transaction_reference' => $response->charges->data[0]->id, + 'transaction_reference' => isset($response->latest_charge) ? $response->latest_charge : $response->charges->data[0]->id, 'amount' => $amount, ]; @@ -162,6 +169,7 @@ class Charge $payment->meta = $cgt->meta; $payment->save(); + $payment_hash->data = array_merge((array) $payment_hash->data, ['payment_intent' => $response, 'amount_with_fee' => $amount]); $payment_hash->payment_id = $payment->id; $payment_hash->save(); diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index d7cab34fab4e..1caf382e567f 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -267,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) { diff --git a/app/PaymentDrivers/StripePaymentDriver.php b/app/PaymentDrivers/StripePaymentDriver.php index ba180985cb06..b0d5751e539a 100644 --- a/app/PaymentDrivers/StripePaymentDriver.php +++ b/app/PaymentDrivers/StripePaymentDriver.php @@ -660,14 +660,22 @@ class StripePaymentDriver extends BaseDriver ], $this->stripe_connect_auth); if ($charge->captured) { - $payment = Payment::query() - ->where('transaction_reference', $transaction['payment_intent']) - ->where('company_id', $request->getCompany()->id) - ->where(function ($query) use ($transaction) { - $query->where('transaction_reference', $transaction['payment_intent']) - ->orWhere('transaction_reference', $transaction['id']); - }) - ->first(); + + $payment = false; + + if(isset($transaction['payment_intent'])) + { + $payment = Payment::query() + ->where('transaction_reference', $transaction['payment_intent']) + ->where('company_id', $request->getCompany()->id) + ->first(); + } + elseif(isset($transaction['id'])) { + $payment = Payment::query() + ->where('transaction_reference', $transaction['id']) + ->where('company_id', $request->getCompany()->id) + ->first(); + } if ($payment) { $payment->status_id = Payment::STATUS_COMPLETED; diff --git a/app/Services/Bank/BankMatchingService.php b/app/Services/Bank/BankMatchingService.php index 4cdb3415c6af..dd15e5397745 100644 --- a/app/Services/Bank/BankMatchingService.php +++ b/app/Services/Bank/BankMatchingService.php @@ -31,32 +31,16 @@ use Illuminate\Support\Facades\Cache; 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 $deleteWhenMissingModels = true; - - public function __construct(private int $company_id, private string $db){} - - public function handle() + public function handle() :void { MultiDB::setDb($this->db); - $this->company = Company::find($this->company_id); - - $this->matchTransactions(); - - - } - - private function matchTransactions() - { - - BankTransaction::where('company_id', $this->company->id) + BankTransaction::where('company_id', $this->company_id) ->where('status_id', BankTransaction::STATUS_UNMATCHED) ->cursor() ->each(function ($bt){ @@ -64,11 +48,11 @@ class BankMatchingService implements ShouldQueue (new BankService($bt))->processRules(); }); - + } public function middleware() { - return [new WithoutOverlapping($this->company_id)]; + return [new WithoutOverlapping("bank_match_rate:{$this->company_id}")]; } } diff --git a/app/Services/Credit/SendEmail.php b/app/Services/Credit/SendEmail.php index c7633f2d62b5..68fee262d4e3 100644 --- a/app/Services/Credit/SendEmail.php +++ b/app/Services/Credit/SendEmail.php @@ -45,7 +45,7 @@ class SendEmail $this->credit->invitations->each(function ($invitation) { if (! $invitation->contact->trashed() && $invitation->contact->email) { - EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template); + EmailEntity::dispatch($invitation, $invitation->company, $this->reminder_template)->delay(2); } }); diff --git a/app/Services/Invoice/AddGatewayFee.php b/app/Services/Invoice/AddGatewayFee.php index 997b89f7976d..4703e7a5673e 100644 --- a/app/Services/Invoice/AddGatewayFee.php +++ b/app/Services/Invoice/AddGatewayFee.php @@ -112,8 +112,7 @@ class AddGatewayFee extends AbstractService $this->invoice ->client ->service() - ->updateBalance($adjustment) - ->save(); + ->updateBalance($adjustment); $this->invoice ->ledger() diff --git a/app/Services/Ledger/LedgerService.php b/app/Services/Ledger/LedgerService.php index 6cb487bb4391..9db3e38658bb 100644 --- a/app/Services/Ledger/LedgerService.php +++ b/app/Services/Ledger/LedgerService.php @@ -36,7 +36,7 @@ class LedgerService $this->entity->company_ledger()->save($company_ledger); - ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300)); + ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300))); return $this; } @@ -52,7 +52,7 @@ class LedgerService $this->entity->company_ledger()->save($company_ledger); - ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300)); + ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300))); return $this; } @@ -68,7 +68,7 @@ class LedgerService $this->entity->company_ledger()->save($company_ledger); - ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(300)); + ClientLedgerBalanceUpdate::dispatch($this->entity->company, $this->entity->client)->delay(now()->addSeconds(rand(30,300))); return $this; } diff --git a/app/Services/Payment/SendEmail.php b/app/Services/Payment/SendEmail.php index be7326098c9c..d9f6907a2610 100644 --- a/app/Services/Payment/SendEmail.php +++ b/app/Services/Payment/SendEmail.php @@ -37,7 +37,7 @@ class SendEmail $contact = $this->payment->client->contacts()->first(); if ($contact?->email) - EmailPayment::dispatch($this->payment, $this->payment->company, $contact); + EmailPayment::dispatch($this->payment, $this->payment->company, $contact)->delay(now()->addSeconds(3)); } } diff --git a/app/Utils/Traits/Inviteable.php b/app/Utils/Traits/Inviteable.php index 595b3a51f23a..86120629acf6 100644 --- a/app/Utils/Traits/Inviteable.php +++ b/app/Utils/Traits/Inviteable.php @@ -70,7 +70,7 @@ trait Inviteable $qr = $writer->writeString($this->getPaymentLink(), 'utf-8'); - return " + return " {$qr}"; } diff --git a/config/ninja.php b/config/ninja.php index 8583baa84408..314b877deb28 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.5.42', - 'app_tag' => '5.5.42', + 'app_version' => '5.5.43', + 'app_tag' => '5.5.43', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), diff --git a/database/migrations/2022_11_22_215618_lock_tasks_when_invoiced.php b/database/migrations/2022_11_22_215618_lock_tasks_when_invoiced.php index 38e465a6c68d..51d9aea95ca9 100644 --- a/database/migrations/2022_11_22_215618_lock_tasks_when_invoiced.php +++ b/database/migrations/2022_11_22_215618_lock_tasks_when_invoiced.php @@ -4,9 +4,12 @@ use App\Models\Currency; use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; +use App\Utils\Traits\AppSetup; return new class extends Migration { + use AppSetup; + /** * Run the migrations. * @@ -65,6 +68,9 @@ return new class extends Migration \Illuminate\Support\Facades\Artisan::call('ninja:design-update'); + $this->buildCache(true); + + } /**