diff --git a/app/Factory/CloneQuoteToInvoiceFactory.php b/app/Factory/CloneQuoteToInvoiceFactory.php index b7da525f48..03d057987a 100644 --- a/app/Factory/CloneQuoteToInvoiceFactory.php +++ b/app/Factory/CloneQuoteToInvoiceFactory.php @@ -30,7 +30,8 @@ class CloneQuoteToInvoiceFactory unset($quote_array['invitations']); //preserve terms if they exist on Quotes - if(array_key_exists('terms', $quote_array) && strlen($quote_array['terms']) < 2) + //if(array_key_exists('terms', $quote_array) && strlen($quote_array['terms']) < 2) + if(!$quote->company->use_quote_terms_on_conversion) unset($quote_array['terms']); // unset($quote_array['public_notes']); @@ -38,7 +39,6 @@ class CloneQuoteToInvoiceFactory unset($quote_array['design_id']); unset($quote_array['user']); - foreach ($quote_array as $key => $value) { $invoice->{$key} = $value; } diff --git a/app/Http/Requests/Report/GenericReportRequest.php b/app/Http/Requests/Report/GenericReportRequest.php index e49bfb872d..95d5a04ac0 100644 --- a/app/Http/Requests/Report/GenericReportRequest.php +++ b/app/Http/Requests/Report/GenericReportRequest.php @@ -32,7 +32,8 @@ class GenericReportRequest extends Request 'end_date' => 'string|date', 'date_key' => 'string', 'date_range' => 'string', - 'report_keys' => 'present|array' + 'report_keys' => 'present|array', + 'send_email' => 'bool', ]; } } diff --git a/app/Http/Requests/Report/ProfitLossRequest.php b/app/Http/Requests/Report/ProfitLossRequest.php index 44fc0cae39..0d4a848234 100644 --- a/app/Http/Requests/Report/ProfitLossRequest.php +++ b/app/Http/Requests/Report/ProfitLossRequest.php @@ -33,7 +33,8 @@ class ProfitLossRequest extends Request 'is_income_billed' => 'required|bail|bool', 'is_expense_billed' => 'required|bail|bool', 'include_tax' => 'required|bail|bool', - 'date_range' => 'required|bail|string' + 'date_range' => 'required|bail|string', + 'send_email' => 'bool', ]; } } diff --git a/app/Jobs/Cron/AutoBillCron.php b/app/Jobs/Cron/AutoBillCron.php index cd58982311..025e6ee682 100644 --- a/app/Jobs/Cron/AutoBillCron.php +++ b/app/Jobs/Cron/AutoBillCron.php @@ -53,6 +53,7 @@ class AutoBillCron $auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now()) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('auto_bill_enabled', true) + ->where('auto_bill_tries', '<', 3) ->where('balance', '>', 0) ->where('is_deleted', false) ->whereHas('company', function ($query) { @@ -70,6 +71,7 @@ class AutoBillCron $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('auto_bill_enabled', true) + ->where('auto_bill_tries', '<', 3) ->where('balance', '>', 0) ->where('is_deleted', false) ->whereHas('company', function ($query) { @@ -94,6 +96,7 @@ class AutoBillCron $auto_bill_partial_invoices = Invoice::whereDate('partial_due_date', '<=', now()) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('auto_bill_enabled', true) + ->where('auto_bill_tries', '<', 3) ->where('balance', '>', 0) ->where('is_deleted', false) ->whereHas('company', function ($query) { @@ -111,6 +114,7 @@ class AutoBillCron $auto_bill_invoices = Invoice::whereDate('due_date', '<=', now()) ->whereIn('status_id', [Invoice::STATUS_SENT, Invoice::STATUS_PARTIAL]) ->where('auto_bill_enabled', true) + ->where('auto_bill_tries', '<', 3) ->where('balance', '>', 0) ->where('is_deleted', false) ->whereHas('company', function ($query) { diff --git a/app/Jobs/Cron/RecurringInvoicesCron.php b/app/Jobs/Cron/RecurringInvoicesCron.php index 19ffaca748..194d17e11d 100644 --- a/app/Jobs/Cron/RecurringInvoicesCron.php +++ b/app/Jobs/Cron/RecurringInvoicesCron.php @@ -67,11 +67,23 @@ class RecurringInvoicesCron nlog("Trying to send {$recurring_invoice->number}"); + /* Special check if we should generate another invoice is the previous one is yet to be paid */ + if($recurring_invoice->company->stop_on_unpaid_recurring && $recurring_invoice->invoices()->whereIn('status_id', [2,3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) { + + nlog("Existing invoice exists, skipping"); + return; + + } + try{ + SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); + } catch(\Exception $e){ + nlog("Unable to sending recurring invoice {$recurring_invoice->id} ". $e->getMessage()); + } }); @@ -103,6 +115,13 @@ class RecurringInvoicesCron nlog("Trying to send {$recurring_invoice->number}"); + if($recurring_invoice->company->stop_on_unpaid_recurring) { + + if($recurring_invoice->invoices()->whereIn('status_id', [2,3])->where('is_deleted', 0)->where('balance', '>', 0)->exists()) + return; + + } + try{ SendRecurring::dispatchNow($recurring_invoice, $recurring_invoice->company->db); } diff --git a/app/Models/Company.php b/app/Models/Company.php index 7ff29a5b3c..40300ae897 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -100,6 +100,8 @@ class Company extends BaseModel 'client_registration_fields', 'convert_rate_to_client', 'markdown_email_enabled', + 'stop_on_unpaid_recurring', + 'use_quote_terms_on_conversion', ]; protected $hidden = [ diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php index c9ab3ea720..f2907f385e 100644 --- a/app/Models/Invoice.php +++ b/app/Models/Invoice.php @@ -90,8 +90,7 @@ class Invoice extends BaseModel 'subscription_id', 'auto_bill_enabled', 'uses_inclusive_taxes', - 'vendor_id', - 'auto_bill_tries' + 'vendor_id' ]; protected $casts = [ diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 7f6317f26b..08ba47d65c 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -227,12 +227,21 @@ class RecurringInvoice extends BaseModel if (!$this->next_send_date) { return null; } - - nlog("frequency = $this->frequency_id"); - nlog("frequency = $this->next_send_date"); $offset = $this->client->timezone_offset(); + /* If this setting is enabled, the recurring invoice may be set in the past */ + + if($this->company->stop_on_unpaid_recurring) { + + /* Lets set the next send date to now so we increment from today, rather than in the past*/ + if(Carbon::parse($this->next_send_date)->lt(now()->subDays(3))) + $this->next_send_date = now()->format('Y-m-d'); + + } + + + /* As we are firing at UTC+0 if our offset is negative it is technically firing the day before so we always need to add ON a day - a day = 86400 seconds diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index a94a7e68f9..6ae012e328 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -117,31 +117,26 @@ class AutoBillInvoice extends AbstractService $payment = false; - $number_of_retries = $this->invoice->auto_bill_tries; - try { $payment = $gateway_token->gateway ->driver($this->client) ->setPaymentHash($payment_hash) ->tokenBilling($gateway_token, $payment_hash); } catch (\Exception $e) { - //increase the counter $this->invoice->auto_bill_tries = $number_of_retries + 1; - $this->invoice->save(); - //disable auto bill if limit of 3 is reached + if ($this->invoice->auto_bill_tries == 3) { $this->invoice->auto_bill_enabled = false; + $this->invoice->auto_bill_tries = 0; //reset the counter here in case auto billing is turned on again in the future. $this->invoice->save(); } nlog("payment NOT captured for " . $this->invoice->number . " with error " . $e->getMessage()); - // $this->invoice->service()->removeUnpaidGatewayFees(); } if ($payment) { info("Auto Bill payment captured for " . $this->invoice->number); } - // return $this->invoice->fresh(); } /** diff --git a/app/Transformers/CompanyTransformer.php b/app/Transformers/CompanyTransformer.php index 82fc9af82f..3bc05c684e 100644 --- a/app/Transformers/CompanyTransformer.php +++ b/app/Transformers/CompanyTransformer.php @@ -168,6 +168,7 @@ class CompanyTransformer extends EntityTransformer 'client_registration_fields' => (array) $company->client_registration_fields, 'convert_rate_to_client' => (bool) $company->convert_rate_to_client, 'markdown_email_enabled' => (bool) $company->markdown_email_enabled, + 'stop_on_unpaid_recurring' => (bool) $company->stop_on_unpaid_recurring, ]; } diff --git a/database/migrations/2022_05_16_224917_add_auto_bill_tries_to_invoices_table.php b/database/migrations/2022_05_16_224917_add_auto_bill_tries_to_invoices_table.php index befc660c0c..5d57b04687 100644 --- a/database/migrations/2022_05_16_224917_add_auto_bill_tries_to_invoices_table.php +++ b/database/migrations/2022_05_16_224917_add_auto_bill_tries_to_invoices_table.php @@ -16,6 +16,11 @@ class AddAutoBillTriesToInvoicesTable extends Migration Schema::table('invoices', function (Blueprint $table) { $table->smallInteger('auto_bill_tries')->default(0); }); + + Schema::table('companies', function (Blueprint $table) { + $table->boolean('stop_on_unpaid_recurring')->default(0); + $table->boolean('use_quote_terms_on_conversion')->default(0); + }); } /** @@ -25,8 +30,6 @@ class AddAutoBillTriesToInvoicesTable extends Migration */ public function down() { - Schema::table('invoices', function (Blueprint $table) { - $table->dropColumn('auto_bill_tries'); - }); + } }