From 9bec6d458b7d871096072613845680a59bae190d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 10:12:57 +1100 Subject: [PATCH 01/11] Improve Stripe ACH Payments with microdeposits --- app/PaymentDrivers/Stripe/ACH.php | 17 +++++++ .../Stripe/Jobs/PaymentIntentWebhook.php | 46 ++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/app/PaymentDrivers/Stripe/ACH.php b/app/PaymentDrivers/Stripe/ACH.php index d602d320f0..2a5689ccf9 100644 --- a/app/PaymentDrivers/Stripe/ACH.php +++ b/app/PaymentDrivers/Stripe/ACH.php @@ -165,6 +165,18 @@ class ACH $data['payment_method_id'] = GatewayType::BANK_TRANSFER; $data['customer'] = $this->stripe->findOrCreateCustomer(); $data['amount'] = $this->stripe->convertToStripeAmount($data['total']['amount_with_fee'], $this->stripe->client->currency()->precision, $this->stripe->client->currency()); + $amount = $data['total']['amount_with_fee']; + + $invoice = Invoice::whereIn('id', $this->transformKeys(array_column($this->stripe->payment_hash->invoices(), 'invoice_id'))) + ->withTrashed() + ->first(); + + if ($invoice) { + $description = "Invoice {$invoice->number} for {$amount} for client {$this->stripe->client->present()->name()}"; + } else { + $description = "Payment with no invoice for amount {$amount} for client {$this->stripe->client->present()->name()}"; + } + $intent = false; @@ -176,6 +188,11 @@ class ACH 'setup_future_usage' => 'off_session', 'customer' => $data['customer']->id, 'payment_method_types' => ['us_bank_account'], + 'description' => $description, + 'metadata' => [ + 'payment_hash' => $this->stripe->payment_hash->hash, + 'gateway_type_id' => GatewayType::BANK_TRANSFER, + ], ] ); } diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index f37f46f5a7..800f13f9f8 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -53,7 +53,8 @@ class PaymentIntentWebhook implements ShouldQueue public function handle() { - + nlog($this->stripe_request); + MultiDB::findAndSetDbByCompanyKey($this->company_key); $company = Company::where('company_key', $this->company_key)->first(); @@ -145,7 +146,18 @@ class PaymentIntentWebhook implements ShouldQueue $this->updateCreditCardPayment($payment_hash, $client); } + elseif(array_key_exists('payment_method_types', $this->stripe_request['object']) && optional($this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']) && in_array('us_bank_account', $this->stripe_request['object']['payment_method_types'])) + { + nlog("hash found"); + $hash = $this->stripe_request['object']['charges']['data'][0]['metadata']['payment_hash']; + + $payment_hash = PaymentHash::where('hash', $hash)->first(); + $invoice = Invoice::with('client')->find($payment_hash->fee_invoice_id); + $client = $invoice->client; + + $this->updateAchPayment($payment_hash, $client); + } } @@ -161,6 +173,38 @@ class PaymentIntentWebhook implements ShouldQueue } + private function updateAchPayment($payment_hash, $client) + { + $company_gateway = CompanyGateway::find($this->company_gateway_id); + $payment_method_type = optional($this->stripe_request['object']['charges']['data'][0]['metadata'])['gateway_type_id']; + $driver = $company_gateway->driver($client)->init()->setPaymentMethod($payment_method_type); + + $payment_hash->data = array_merge((array) $payment_hash->data, $this->stripe_request); + $payment_hash->save(); + $driver->setPaymentHash($payment_hash); + + $data = [ + 'payment_method' => $payment_hash->data->object->payment_method, + 'payment_type' => PaymentType::ACH, + 'amount' => $payment_hash->data->amount_with_fee, + 'transaction_reference' => $this->stripe_request['object']['charges']['data'][0]['id'], + 'gateway_type_id' => GatewayType::BANK_TRANSFER, + ]; + + $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) { $company_gateway = CompanyGateway::find($this->company_gateway_id); From c7bf4b630b35b64e710e74699e68bf846244148e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 10:24:43 +1100 Subject: [PATCH 02/11] Improve Stripe ACH Payments with microdeposits --- .../portal/ninja2020/gateways/stripe/ach/pay.blade.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php b/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php index 31e56758f9..784aa182d0 100644 --- a/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/stripe/ach/pay.blade.php @@ -207,8 +207,10 @@ gateway_response.value = JSON.stringify(paymentIntent); document.getElementById('server-response').submit(); - } else if (paymentIntent.next_action?.type === "verify_with_microdeposits") { - + } else if (paymentIntent.next_action?.type === "verify_with_microdeposits" || paymentIntent.next_action?.type === "requires_source_action") { + errors.textContent = "You will receive an email with details on how to verify your bank account and process payment."; + errors.hidden = false; + document.getElementById('new-bank').style.visibility = 'hidden' } }); From 6cd6c218dec47babee163e18e3dfe1ebc2fb2449 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 11:00:39 +1100 Subject: [PATCH 03/11] Improve ACH flow --- .../Stripe/Jobs/PaymentIntentWebhook.php | 46 ++++++++++++++++++- .../Stripe/UpdatePaymentMethods.php | 2 +- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php index 800f13f9f8..6b00a1221a 100644 --- a/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php +++ b/app/PaymentDrivers/Stripe/Jobs/PaymentIntentWebhook.php @@ -13,6 +13,7 @@ namespace App\PaymentDrivers\Stripe\Jobs; use App\Jobs\Util\SystemLogger; use App\Libraries\MultiDB; +use App\Models\ClientGatewayToken; use App\Models\Company; use App\Models\CompanyGateway; use App\Models\GatewayType; @@ -21,6 +22,7 @@ use App\Models\Payment; use App\Models\PaymentHash; use App\Models\PaymentType; use App\Models\SystemLog; +use App\PaymentDrivers\Stripe\UpdatePaymentMethods; use App\PaymentDrivers\Stripe\Utilities; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; @@ -53,7 +55,6 @@ class PaymentIntentWebhook implements ShouldQueue public function handle() { - nlog($this->stripe_request); MultiDB::findAndSetDbByCompanyKey($this->company_key); @@ -202,6 +203,49 @@ class PaymentIntentWebhook implements ShouldQueue $client->company, ); + try { + + $customer = $driver->getCustomer($this->stripe_request['object']['charges']['data'][0]['customer']); + $method = $driver->getStripePaymentMethod($this->stripe_request['object']['charges']['data'][0]['payment_method']); + $payment_method = $this->stripe_request['object']['charges']['data'][0]['payment_method']; + + $token_exists = ClientGatewayToken::where([ + 'gateway_customer_reference' => $customer->id, + 'token' => $payment_method, + 'client_id' => $client->id, + 'company_id' => $client->company_id, + ])->exists(); + + /* Already exists return */ + if ($token_exists) { + return; + } + + $payment_meta = new \stdClass; + $payment_meta->brand = (string) \sprintf('%s (%s)', $method->us_bank_account['bank_name'], ctrans('texts.ach')); + $payment_meta->last4 = (string) $method->us_bank_account['last4']; + $payment_meta->type = GatewayType::BANK_TRANSFER; + $payment_meta->state = 'verified'; + + $data = [ + 'payment_meta' => $payment_meta, + 'token' => $payment_method, + 'payment_method_id' => GatewayType::BANK_TRANSFER, + ]; + + $additional_data = ['gateway_customer_reference' => $customer->id]; + + if ($customer->default_source === $method->id) { + $additional_data = ['gateway_customer_reference' => $customer->id, 'is_default' => 1]; + } + + $driver->storeGatewayToken($data, $additional_data); + + } + catch(\Exception $e){ + nlog("failed to import payment methods"); + nlog($e->getMessage()); + } } diff --git a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php index 4424ca40ee..ed333de662 100644 --- a/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php +++ b/app/PaymentDrivers/Stripe/UpdatePaymentMethods.php @@ -70,7 +70,7 @@ class UpdatePaymentMethods $this->importBankAccounts($customer, $client); } - private function importBankAccounts($customer, $client) + public function importBankAccounts($customer, $client) { $sources = $customer->sources; From 414c1c3255bd2d26c85886af11828cd925d94a75 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 12:14:31 +1100 Subject: [PATCH 04/11] Add index to product_key: --- .../2022_10_06_011344_add_key_to_products.php | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 database/migrations/2022_10_06_011344_add_key_to_products.php diff --git a/database/migrations/2022_10_06_011344_add_key_to_products.php b/database/migrations/2022_10_06_011344_add_key_to_products.php new file mode 100644 index 0000000000..cfbce3e275 --- /dev/null +++ b/database/migrations/2022_10_06_011344_add_key_to_products.php @@ -0,0 +1,32 @@ +index(['product_key', 'company_id']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('products', function (Blueprint $table) { + // + }); + } +}; From 63ea5abebf34636c8704d716b61f7dc61aa90959 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 12:54:30 +1100 Subject: [PATCH 05/11] Notifications --- .../Ninja/DomainFailureNotification.php | 86 +++++++++++++++++++ .../Ninja/NewAccountNotification.php | 2 - 2 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 app/Notifications/Ninja/DomainFailureNotification.php diff --git a/app/Notifications/Ninja/DomainFailureNotification.php b/app/Notifications/Ninja/DomainFailureNotification.php new file mode 100644 index 0000000000..95ddaf032b --- /dev/null +++ b/app/Notifications/Ninja/DomainFailureNotification.php @@ -0,0 +1,86 @@ +domain = $domain; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['slack']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return MailMessage + */ + public function toMail($notifiable) + { + } + + /** + * Get the array representation of the notification. + * + * @param mixed $notifiable + * @return array + */ + public function toArray($notifiable) + { + return [ + // + ]; + } + + public function toSlack($notifiable) + { + $content = "Domain Certificate failure:\n"; + $content .= "{$this->domain}\n"; + + return (new SlackMessage) + ->success() + ->from(ctrans('texts.notification_bot')) + ->image('https://app.invoiceninja.com/favicon.png') + ->content($content); + } +} diff --git a/app/Notifications/Ninja/NewAccountNotification.php b/app/Notifications/Ninja/NewAccountNotification.php index 1cd13b8d55..14f5a227fd 100644 --- a/app/Notifications/Ninja/NewAccountNotification.php +++ b/app/Notifications/Ninja/NewAccountNotification.php @@ -79,8 +79,6 @@ class NewAccountNotification extends Notification { $content = "New Trial Started\n"; $content .= "{$this->client->name}\n"; - $content .= "Account key: {$this->account->key}\n"; - $content .= "Users: {$this->account->users()->pluck('email')}\n"; $content .= "Contacts: {$this->client->contacts()->pluck('email')}\n"; From b88e47e9d2e35fc8e25076fb7516ccc24add11fd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 13:48:04 +1100 Subject: [PATCH 06/11] minor fixes for expense import date --- app/Import/Transformer/Csv/ExpenseTransformer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Import/Transformer/Csv/ExpenseTransformer.php b/app/Import/Transformer/Csv/ExpenseTransformer.php index 2e5cb032dc..10f727bf7d 100644 --- a/app/Import/Transformer/Csv/ExpenseTransformer.php +++ b/app/Import/Transformer/Csv/ExpenseTransformer.php @@ -42,7 +42,7 @@ class ExpenseTransformer extends BaseTransformer 'client_id' => isset($data['expense.client']) ? $this->getClientId($data['expense.client']) : null, - 'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime($this->getString($data, 'expense.date'))) : now()->format('Y-m-d'), + 'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime($data['expense.date'])) : now()->format('Y-m-d'), 'public_notes' => $this->getString($data, 'expense.public_notes'), 'private_notes' => $this->getString($data, 'expense.private_notes'), 'category_id' => isset($data['expense.category']) From 3c4dd84a4f61f93e915292f97e2f901666f0649c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 14:28:57 +1100 Subject: [PATCH 07/11] Improving CSV Expense imports --- app/Import/Definitions/ExpenseMap.php | 18 ++++++++++++++++++ .../Transformer/Csv/ExpenseTransformer.php | 12 ++++++++++-- .../Transformer/Csv/InvoiceTransformer.php | 4 ++-- .../Transformer/Csv/QuoteTransformer.php | 4 ++-- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/Import/Definitions/ExpenseMap.php b/app/Import/Definitions/ExpenseMap.php index ae530a7119..a0df8ed876 100644 --- a/app/Import/Definitions/ExpenseMap.php +++ b/app/Import/Definitions/ExpenseMap.php @@ -28,6 +28,15 @@ class ExpenseMap 9 => 'expense.transaction_reference', 10 => 'expense.public_notes', 11 => 'expense.private_notes', + 12 => 'expense.tax_name1', + 13 => 'expense.tax_rate1', + 14 => 'expense.tax_name2', + 15 => 'expense.tax_rate2', + 16 => 'expense.tax_name3', + 17 => 'expense.tax_rate3', + 18 => 'expense.uses_inclusive_taxes', + 19 => 'expense.payment_date', + ]; } @@ -46,6 +55,15 @@ class ExpenseMap 9 => 'texts.transaction_reference', 10 => 'texts.public_notes', 11 => 'texts.private_notes', + 12 => 'texts.tax_name1', + 13 => 'texts.tax_rate1', + 14 => 'texts.tax_name2', + 15 => 'texts.tax_rate2', + 16 => 'texts.tax_name3', + 17 => 'texts.tax_rate3', + 18 => 'texts.uses_inclusive_taxes', + 19 => 'texts.payment_date', + ]; } } diff --git a/app/Import/Transformer/Csv/ExpenseTransformer.php b/app/Import/Transformer/Csv/ExpenseTransformer.php index 10f727bf7d..ef0167c61b 100644 --- a/app/Import/Transformer/Csv/ExpenseTransformer.php +++ b/app/Import/Transformer/Csv/ExpenseTransformer.php @@ -42,7 +42,7 @@ class ExpenseTransformer extends BaseTransformer 'client_id' => isset($data['expense.client']) ? $this->getClientId($data['expense.client']) : null, - 'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime($data['expense.date'])) : now()->format('Y-m-d'), + 'date' => strlen($this->getString($data, 'expense.date') > 1) ? date('Y-m-d', strtotime(str_replace("/","-",$data['expense.date']))) : now()->format('Y-m-d'), 'public_notes' => $this->getString($data, 'expense.public_notes'), 'private_notes' => $this->getString($data, 'expense.private_notes'), 'category_id' => isset($data['expense.category']) @@ -55,7 +55,7 @@ class ExpenseTransformer extends BaseTransformer ? $this->getPaymentTypeId($data['expense.payment_type']) : null, 'payment_date' => isset($data['expense.payment_date']) - ? date('Y-m-d', strtotime($data['expense.payment_date'])) + ? date('Y-m-d', strtotime(str_replace("/","-",$data['expense.payment_date']))) : null, 'custom_value1' => $this->getString($data, 'expense.custom_value1'), 'custom_value2' => $this->getString($data, 'expense.custom_value2'), @@ -66,6 +66,14 @@ class ExpenseTransformer extends BaseTransformer 'expense.transaction_reference' ), 'should_be_invoiced' => $clientId ? true : false, + 'uses_inclusive_taxes' => (bool) $this->getString($data, 'expense.uses_inclusive_taxes'), + 'tax_name1' => $this->getString($data, 'expense.tax_name1'), + 'tax_rate1' => $this->getFloat($data, 'expense.tax_rate1'), + 'tax_name2' => $this->getString($data, 'expense.tax_name2'), + 'tax_rate2' => $this->getFloat($data, 'expense.tax_rate2'), + 'tax_name3' => $this->getString($data, 'expense.tax_name3'), + 'tax_rate3' => $this->getFloat($data, 'expense.tax_rate3'), + ]; } } diff --git a/app/Import/Transformer/Csv/InvoiceTransformer.php b/app/Import/Transformer/Csv/InvoiceTransformer.php index 643f201437..7648651c82 100644 --- a/app/Import/Transformer/Csv/InvoiceTransformer.php +++ b/app/Import/Transformer/Csv/InvoiceTransformer.php @@ -57,10 +57,10 @@ class InvoiceTransformer extends BaseTransformer 'discount' => $this->getFloat($invoice_data, 'invoice.discount'), 'po_number' => $this->getString($invoice_data, 'invoice.po_number'), 'date' => isset($invoice_data['invoice.date']) - ? date('Y-m-d', strtotime($invoice_data['invoice.date'])) + ? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.date']))) : now()->format('Y-m-d'), 'due_date' => isset($invoice_data['invoice.due_date']) - ? date('Y-m-d', strtotime($invoice_data['invoice.due_date'])) + ? date('Y-m-d', strtotime(str_replace("/","-",$invoice_data['invoice.due_date']))) : null, 'terms' => $this->getString($invoice_data, 'invoice.terms'), 'public_notes' => $this->getString( diff --git a/app/Import/Transformer/Csv/QuoteTransformer.php b/app/Import/Transformer/Csv/QuoteTransformer.php index 59d3bcb85d..8c1add9da9 100644 --- a/app/Import/Transformer/Csv/QuoteTransformer.php +++ b/app/Import/Transformer/Csv/QuoteTransformer.php @@ -57,10 +57,10 @@ class QuoteTransformer extends BaseTransformer 'discount' => $this->getFloat($quote_data, 'quote.discount'), 'po_number' => $this->getString($quote_data, 'quote.po_number'), 'date' => isset($quote_data['quote.date']) - ? date('Y-m-d', strtotime($quote_data['quote.date'])) + ? date('Y-m-d', strtotime(str_replace("/","-",$quote_data['quote.date']))) : now()->format('Y-m-d'), 'due_date' => isset($quote_data['quote.due_date']) - ? date('Y-m-d', strtotime($quote_data['quote.due_date'])) + ? date('Y-m-d', strtotime(str_replace("/","-",$quote_data['quote.due_date']))) : null, 'terms' => $this->getString($quote_data, 'quote.terms'), 'public_notes' => $this->getString( From e62e2d5fc985e55b045d95275becf7248fe64377 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 16:37:39 +1100 Subject: [PATCH 08/11] Fixes for low permission users hitting list views --- app/Http/Controllers/BaseController.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/BaseController.php b/app/Http/Controllers/BaseController.php index 4728fdd5a3..b004034e5d 100644 --- a/app/Http/Controllers/BaseController.php +++ b/app/Http/Controllers/BaseController.php @@ -773,8 +773,16 @@ class BaseController extends Controller // 10-01-2022 need to ensure we snake case properly here to ensure permissions work as expected // 28-03-2022 this is definitely correct here, do not append _ to the view, it resolved correctly when snake cased if (auth()->user() && ! auth()->user()->hasPermission('view'.lcfirst(class_basename(Str::snake($this->entity_type))))) { - //03-09-2022 - $query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id); + + //06-10-2022 - some entities do not have assigned_user_id - this becomes an issue when we have a large company and low permission users + if(lcfirst(class_basename(Str::snake($this->entity_type))) == 'user') + $query->where('id', auth()->user()->id); + elseif(in_array(lcfirst(class_basename(Str::snake($this->entity_type))),['design','group_setting','payment_term'])){ + //need to pass these back regardless + } + else + $query->where('user_id', '=', auth()->user()->id)->orWhere('assigned_user_id', auth()->user()->id); + } if (request()->has('updated_at') && request()->input('updated_at') > 0) { From 0a405f9329d06f8f6fd4504352513429536d4701 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 18:54:13 +1100 Subject: [PATCH 09/11] More indexing for DB queries --- .../2022_10_06_011344_add_key_to_products.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/database/migrations/2022_10_06_011344_add_key_to_products.php b/database/migrations/2022_10_06_011344_add_key_to_products.php index cfbce3e275..3b5e03f215 100644 --- a/database/migrations/2022_10_06_011344_add_key_to_products.php +++ b/database/migrations/2022_10_06_011344_add_key_to_products.php @@ -16,6 +16,17 @@ return new class extends Migration Schema::table('products', function (Blueprint $table) { $table->index(['product_key', 'company_id']); }); + + Schema::table('companies', function (Blueprint $table) { + $table->index(['subdomain', 'portal_mode']); + $table->index(['portal_domain', 'portal_mode']); + $table->index('company_key'); + }); + + Schema::table('accounts', function (Blueprint $table) { + $table->index('key'); + }); + } /** @@ -25,8 +36,6 @@ return new class extends Migration */ public function down() { - Schema::table('products', function (Blueprint $table) { - // - }); + } }; From d9fac20cfff242ed6d3e835206a98018e559a650 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 19:03:41 +1100 Subject: [PATCH 10/11] Fixes for nullable timestamp columns in scheduler --- ...2_05_18_162443_create_schedulers_table.php | 6 +- database/schema/db-ninja-01-schema.dump | 88 +++++++++++++------ 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/database/migrations/2022_05_18_162443_create_schedulers_table.php b/database/migrations/2022_05_18_162443_create_schedulers_table.php index d8c5bb8d18..26a9c7ce90 100644 --- a/database/migrations/2022_05_18_162443_create_schedulers_table.php +++ b/database/migrations/2022_05_18_162443_create_schedulers_table.php @@ -26,8 +26,8 @@ return new class extends Migration { $table->boolean('paused')->default(false); $table->boolean('is_deleted')->default(false); $table->string('repeat_every'); - $table->timestamp('start_from'); - $table->timestamp('scheduled_run'); + $table->timestamp('start_from')->nullable(); + $table->timestamp('scheduled_run')->nullable(); $table->foreignIdFor(\App\Models\Company::class); $table->timestamps(); $table->softDeletes(); @@ -41,6 +41,6 @@ return new class extends Migration { */ public function down() { - Schema::dropIfExists('schedulers'); + } }; diff --git a/database/schema/db-ninja-01-schema.dump b/database/schema/db-ninja-01-schema.dump index 8c4198c363..0d25435731 100644 --- a/database/schema/db-ninja-01-schema.dump +++ b/database/schema/db-ninja-01-schema.dump @@ -44,11 +44,12 @@ CREATE TABLE `accounts` ( `set_react_as_default_ap` tinyint(1) NOT NULL DEFAULT '0', `is_flagged` tinyint(1) NOT NULL DEFAULT '0', `is_verified_account` tinyint(1) NOT NULL DEFAULT '0', - `account_sms_verification_code` text COLLATE utf8mb4_unicode_ci, - `account_sms_verification_number` text COLLATE utf8mb4_unicode_ci, + `account_sms_verification_code` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `account_sms_verification_number` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `account_sms_verified` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), - KEY `accounts_payment_id_index` (`payment_id`) + KEY `accounts_payment_id_index` (`payment_id`), + KEY `accounts_key_index` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; /*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `activities`; @@ -222,6 +223,8 @@ CREATE TABLE `client_contacts` ( KEY `client_contacts_company_id_index` (`company_id`), KEY `client_contacts_client_id_index` (`client_id`), KEY `client_contacts_user_id_index` (`user_id`), + KEY `client_contacts_contact_key(20)_index` (`contact_key`(20)), + KEY `client_contacts_email_index` (`email`), CONSTRAINT `client_contacts_client_id_foreign` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -333,6 +336,7 @@ CREATE TABLE `clients` ( KEY `clients_size_id_foreign` (`size_id`), KEY `clients_company_id_index` (`company_id`), KEY `clients_user_id_index` (`user_id`), + KEY `clients_client_hash(20)_index` (`client_hash`(20)), CONSTRAINT `clients_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `clients_industry_id_foreign` FOREIGN KEY (`industry_id`) REFERENCES `industries` (`id`), CONSTRAINT `clients_size_id_foreign` FOREIGN KEY (`size_id`) REFERENCES `sizes` (`id`) @@ -410,11 +414,16 @@ CREATE TABLE `companies` ( `inventory_notification_threshold` int NOT NULL DEFAULT '0', `stock_notification` tinyint(1) NOT NULL DEFAULT '1', `enabled_expense_tax_rates` int unsigned NOT NULL DEFAULT '0', + `invoice_task_project` tinyint(1) NOT NULL DEFAULT '0', + `report_include_deleted` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `companies_company_key_unique` (`company_key`), KEY `companies_industry_id_foreign` (`industry_id`), KEY `companies_size_id_foreign` (`size_id`), KEY `companies_account_id_index` (`account_id`), + KEY `companies_subdomain_portal_mode_index` (`subdomain`,`portal_mode`), + KEY `companies_portal_domain_portal_mode_index` (`portal_domain`,`portal_mode`), + KEY `companies_company_key_index` (`company_key`), CONSTRAINT `companies_account_id_foreign` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `companies_industry_id_foreign` FOREIGN KEY (`industry_id`) REFERENCES `industries` (`id`), CONSTRAINT `companies_size_id_foreign` FOREIGN KEY (`size_id`) REFERENCES `sizes` (`id`) @@ -848,7 +857,7 @@ DROP TABLE IF EXISTS `failed_jobs`; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `failed_jobs` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `uuid` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `uuid` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `connection` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `queue` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `payload` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, @@ -1025,6 +1034,8 @@ CREATE TABLE `invoices` ( KEY `invoices_company_id_deleted_at_index` (`company_id`,`deleted_at`), KEY `invoices_client_id_index` (`client_id`), KEY `invoices_company_id_index` (`company_id`), + KEY `invoices_recurring_id_index` (`recurring_id`), + KEY `invoices_status_id_balance_index` (`status_id`,`balance`), CONSTRAINT `invoices_client_id_foreign` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `invoices_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `invoices_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE @@ -1070,6 +1081,7 @@ CREATE TABLE `licenses` ( `is_claimed` tinyint(1) DEFAULT NULL, `transaction_reference` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `product_id` int unsigned DEFAULT NULL, + `recurring_invoice_id` bigint unsigned DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `licenses_license_key_unique` (`license_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; @@ -1169,6 +1181,7 @@ CREATE TABLE `paymentables` ( `deleted_at` timestamp(6) NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `paymentables_payment_id_foreign` (`payment_id`), + KEY `paymentables_paymentable_id_index` (`paymentable_id`), CONSTRAINT `paymentables_payment_id_foreign` FOREIGN KEY (`payment_id`) REFERENCES `payments` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; /*!40101 SET character_set_client = @saved_cs_client */; @@ -1210,7 +1223,9 @@ CREATE TABLE `payments` ( `custom_value2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `custom_value3` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `custom_value4` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `idempotency_key` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), + UNIQUE KEY `payments_company_id_idempotency_key_unique` (`company_id`,`idempotency_key`), KEY `payments_company_id_deleted_at_index` (`company_id`,`deleted_at`), KEY `payments_client_contact_id_foreign` (`client_contact_id`), KEY `payments_company_gateway_id_foreign` (`company_gateway_id`), @@ -1218,6 +1233,8 @@ CREATE TABLE `payments` ( KEY `payments_company_id_index` (`company_id`), KEY `payments_client_id_index` (`client_id`), KEY `payments_status_id_index` (`status_id`), + KEY `payments_transaction_reference_index` (`transaction_reference`), + KEY `payments_idempotency_key_index` (`idempotency_key`), CONSTRAINT `payments_client_contact_id_foreign` FOREIGN KEY (`client_contact_id`) REFERENCES `client_contacts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `payments_client_id_foreign` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `payments_company_gateway_id_foreign` FOREIGN KEY (`company_gateway_id`) REFERENCES `company_gateways` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, @@ -1262,6 +1279,7 @@ CREATE TABLE `products` ( KEY `products_user_id_foreign` (`user_id`), KEY `products_company_id_index` (`company_id`), KEY `pro_co_us_up_index` (`company_id`,`user_id`,`assigned_user_id`,`updated_at`), + KEY `products_product_key_company_id_index` (`product_key`,`company_id`), CONSTRAINT `products_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `products_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8; @@ -1308,11 +1326,11 @@ CREATE TABLE `purchase_order_invitations` ( `user_id` int unsigned NOT NULL, `vendor_contact_id` int unsigned NOT NULL, `purchase_order_id` bigint unsigned NOT NULL, - `key` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `transaction_reference` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `message_id` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `email_error` mediumtext COLLATE utf8mb4_unicode_ci, - `signature_base64` text COLLATE utf8mb4_unicode_ci, + `key` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `transaction_reference` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `message_id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `email_error` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `signature_base64` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `signature_date` datetime DEFAULT NULL, `sent_date` datetime DEFAULT NULL, `viewed_date` datetime DEFAULT NULL, @@ -1320,6 +1338,7 @@ CREATE TABLE `purchase_order_invitations` ( `created_at` timestamp(6) NULL DEFAULT NULL, `updated_at` timestamp(6) NULL DEFAULT NULL, `deleted_at` timestamp(6) NULL DEFAULT NULL, + `email_status` enum('delivered','bounced','spam') COLLATE utf8mb4_unicode_ci DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `vendor_purchase_unique` (`vendor_contact_id`,`purchase_order_id`), KEY `purchase_order_invitations_user_id_foreign` (`user_id`), @@ -1349,25 +1368,25 @@ CREATE TABLE `purchase_orders` ( `recurring_id` int unsigned DEFAULT NULL, `design_id` int unsigned DEFAULT NULL, `invoice_id` int unsigned DEFAULT NULL, - `number` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `number` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `discount` double(8,2) NOT NULL DEFAULT '0.00', `is_amount_discount` tinyint(1) NOT NULL DEFAULT '0', - `po_number` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `po_number` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `date` date DEFAULT NULL, `last_sent_date` datetime DEFAULT NULL, `due_date` date DEFAULT NULL, `is_deleted` tinyint(1) NOT NULL DEFAULT '0', - `line_items` mediumtext COLLATE utf8mb4_unicode_ci, - `backup` mediumtext COLLATE utf8mb4_unicode_ci, - `footer` text COLLATE utf8mb4_unicode_ci, - `public_notes` text COLLATE utf8mb4_unicode_ci, - `private_notes` text COLLATE utf8mb4_unicode_ci, - `terms` text COLLATE utf8mb4_unicode_ci, - `tax_name1` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `line_items` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `backup` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `footer` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `public_notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `private_notes` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `terms` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `tax_name1` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `tax_rate1` decimal(20,6) NOT NULL DEFAULT '0.000000', - `tax_name2` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `tax_name2` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `tax_rate2` decimal(20,6) NOT NULL DEFAULT '0.000000', - `tax_name3` varchar(191) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `tax_name3` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `tax_rate3` decimal(20,6) NOT NULL DEFAULT '0.000000', `total_taxes` decimal(20,6) NOT NULL DEFAULT '0.000000', `uses_inclusive_taxes` tinyint(1) NOT NULL DEFAULT '0', @@ -1375,10 +1394,10 @@ CREATE TABLE `purchase_orders` ( `reminder2_sent` date DEFAULT NULL, `reminder3_sent` date DEFAULT NULL, `reminder_last_sent` date DEFAULT NULL, - `custom_value1` text COLLATE utf8mb4_unicode_ci, - `custom_value2` text COLLATE utf8mb4_unicode_ci, - `custom_value3` text COLLATE utf8mb4_unicode_ci, - `custom_value4` text COLLATE utf8mb4_unicode_ci, + `custom_value1` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `custom_value2` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `custom_value3` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, + `custom_value4` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, `next_send_date` datetime DEFAULT NULL, `custom_surcharge1` decimal(20,6) DEFAULT NULL, `custom_surcharge2` decimal(20,6) DEFAULT NULL, @@ -1517,6 +1536,7 @@ CREATE TABLE `quotes` ( KEY `quotes_company_id_deleted_at_index` (`company_id`,`deleted_at`), KEY `quotes_client_id_index` (`client_id`), KEY `quotes_company_id_index` (`company_id`), + KEY `quotes_company_id_updated_at_index` (`company_id`,`updated_at`), CONSTRAINT `quotes_client_id_foreign` FOREIGN KEY (`client_id`) REFERENCES `clients` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `quotes_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `quotes_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE @@ -1813,16 +1833,16 @@ CREATE TABLE `schedulers` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `paused` tinyint(1) NOT NULL DEFAULT '0', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', - `repeat_every` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, + `repeat_every` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `start_from` timestamp NOT NULL, `scheduled_run` timestamp NOT NULL, `company_id` bigint unsigned NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, `deleted_at` timestamp NULL DEFAULT NULL, - `action_name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `action_class` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL, - `parameters` mediumtext COLLATE utf8mb4_unicode_ci, + `action_name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `action_class` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `parameters` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci, PRIMARY KEY (`id`), KEY `schedulers_action_name_index` (`action_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC; @@ -2123,6 +2143,8 @@ CREATE TABLE `vendor_contacts` ( KEY `vendor_contacts_user_id_foreign` (`user_id`), KEY `vendor_contacts_vendor_id_index` (`vendor_id`), KEY `vendor_contacts_company_id_email_deleted_at_index` (`company_id`,`email`,`deleted_at`), + KEY `vendor_contacts_contact_key(20)_index` (`contact_key`(20)), + KEY `vendor_contacts_email_index` (`email`), CONSTRAINT `vendor_contacts_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `vendor_contacts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `vendor_contacts_vendor_id_foreign` FOREIGN KEY (`vendor_id`) REFERENCES `vendors` (`id`) ON DELETE CASCADE ON UPDATE CASCADE @@ -2359,3 +2381,13 @@ INSERT INTO `migrations` VALUES (154,'2022_07_21_023805_add_hebrew_language',11) INSERT INTO `migrations` VALUES (155,'2022_07_26_091216_add_sms_verification_to_hosted_account',11); INSERT INTO `migrations` VALUES (156,'2022_07_28_232340_enabled_expense_tax_rates_to_companies_table',11); INSERT INTO `migrations` VALUES (157,'2022_07_29_091235_correction_for_companies_table_types',11); +INSERT INTO `migrations` VALUES (158,'2022_08_11_011534_licenses_table_for_self_host',12); +INSERT INTO `migrations` VALUES (159,'2022_08_24_215917_invoice_task_project_companies_table',12); +INSERT INTO `migrations` VALUES (160,'2022_08_26_232500_add_email_status_column_to_purchase_order_invitations_table',12); +INSERT INTO `migrations` VALUES (161,'2022_08_28_210111_add_index_to_payments_table',12); +INSERT INTO `migrations` VALUES (162,'2022_09_05_024719_update_designs_for_tech_template',12); +INSERT INTO `migrations` VALUES (163,'2022_09_07_101731_add_reporting_option_to_companies_table',12); +INSERT INTO `migrations` VALUES (164,'2022_09_21_012417_add_threeds_to_braintree',12); +INSERT INTO `migrations` VALUES (165,'2022_09_30_235337_add_idempotency_key_to_payments',12); +INSERT INTO `migrations` VALUES (166,'2022_10_05_205645_add_indexes_to_client_hash',12); +INSERT INTO `migrations` VALUES (167,'2022_10_06_011344_add_key_to_products',12); From e294b6fceeaa04c226f3cb4e1441632a8e9d21fc Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 6 Oct 2022 19:04:15 +1100 Subject: [PATCH 11/11] v5.5.27 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 35586e6b16..7c3659f6ec 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.5.26 \ No newline at end of file +5.5.27 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 49a46eb4a8..bb9761e97d 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.26', - 'app_tag' => '5.5.26', + 'app_version' => '5.5.27', + 'app_tag' => '5.5.27', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),