diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index 961e344291..404898086b 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -204,9 +204,9 @@ class RecurringInvoiceController extends BaseController { $recurring_invoice = $this->recurring_invoice_repo->save($request->all(), RecurringInvoiceFactory::create(auth()->user()->company()->id, auth()->user()->id)); - $offset = $recurring_invoice->client->timezone_offset(); - $recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset); - $recurring_invoice->saveQuietly(); + // $offset = $recurring_invoice->client->timezone_offset(); + // $recurring_invoice->next_send_date = Carbon::parse($recurring_invoice->next_send_date)->startOfDay()->addSeconds($offset); + // $recurring_invoice->saveQuietly(); $recurring_invoice->service() ->triggeredActions($request) diff --git a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php index f84e7bf4bc..177b7adbff 100644 --- a/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/StoreRecurringExpenseRequest.php @@ -55,6 +55,10 @@ class StoreRecurringExpenseRequest extends Request $input = $this->decodePrimaryKeys($input); + if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) { + $input['next_send_date_client'] = $input['next_send_date']; + } + if (array_key_exists('category_id', $input) && is_string($input['category_id'])) { $input['category_id'] = $this->decodePrimaryKey($input['category_id']); } diff --git a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php index 8950b04641..745282b7fe 100644 --- a/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php +++ b/app/Http/Requests/RecurringExpense/UpdateRecurringExpenseRequest.php @@ -66,6 +66,10 @@ class UpdateRecurringExpenseRequest extends Request $input = $this->decodePrimaryKeys($input); + if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) { + $input['next_send_date_client'] = $input['next_send_date']; + } + if (array_key_exists('category_id', $input) && is_string($input['category_id'])) { $input['category_id'] = $this->decodePrimaryKey($input['category_id']); } diff --git a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php index 8e20664d3a..087acdca7f 100644 --- a/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/StoreRecurringInvoiceRequest.php @@ -67,6 +67,10 @@ class StoreRecurringInvoiceRequest extends Request { $input = $this->all(); + if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) { + $input['next_send_date_client'] = $input['next_send_date']; + } + if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { $input['design_id'] = $this->decodePrimaryKey($input['design_id']); } diff --git a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php index 63f645d7a9..e697de2543 100644 --- a/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php +++ b/app/Http/Requests/RecurringInvoice/UpdateRecurringInvoiceRequest.php @@ -61,6 +61,10 @@ class UpdateRecurringInvoiceRequest extends Request { $input = $this->all(); + if (array_key_exists('next_send_date', $input) && is_string($input['next_send_date'])) { + $input['next_send_date_client'] = $input['next_send_date']; + } + if (array_key_exists('design_id', $input) && is_string($input['design_id'])) { $input['design_id'] = $this->decodePrimaryKey($input['design_id']); } diff --git a/app/Jobs/Cron/RecurringExpensesCron.php b/app/Jobs/Cron/RecurringExpensesCron.php index f2da548853..8b8c0d8a8c 100644 --- a/app/Jobs/Cron/RecurringExpensesCron.php +++ b/app/Jobs/Cron/RecurringExpensesCron.php @@ -94,6 +94,8 @@ class RecurringExpensesCron $expense->save(); $recurring_expense->next_send_date = $recurring_expense->nextSendDate(); + $recurring_expense->next_send_date_client = $recurring_expense->next_send_date; + $recurring_expense->remaining_cycles = $recurring_expense->remainingCycles(); $recurring_expense->save(); } diff --git a/app/Jobs/RecurringInvoice/SendRecurring.php b/app/Jobs/RecurringInvoice/SendRecurring.php index b060677626..b5ef1d7d20 100644 --- a/app/Jobs/RecurringInvoice/SendRecurring.php +++ b/app/Jobs/RecurringInvoice/SendRecurring.php @@ -105,6 +105,7 @@ class SendRecurring implements ShouldQueue nlog("updating recurring invoice dates"); /* Set next date here to prevent a recurring loop forming */ $this->recurring_invoice->next_send_date = $this->recurring_invoice->nextSendDate(); + $this->recurring_invoice->next_send_date_client = $this->recurring_invoice->nextSendDateClient(); $this->recurring_invoice->remaining_cycles = $this->recurring_invoice->remainingCycles(); $this->recurring_invoice->last_sent_date = now(); diff --git a/app/Models/Client.php b/app/Models/Client.php index 8e692393db..2a00e87ff2 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -667,6 +667,8 @@ class Client extends BaseModel implements HasLocalePreference $offset -= $timezone->utc_offset; $offset += ($entity_send_time * 3600); + nlog("offset = {$offset}"); + return $offset; } diff --git a/app/Models/RecurringExpense.php b/app/Models/RecurringExpense.php index dbcb79efb2..81423f4fd6 100644 --- a/app/Models/RecurringExpense.php +++ b/app/Models/RecurringExpense.php @@ -63,6 +63,7 @@ class RecurringExpense extends BaseModel 'last_sent_date', 'next_send_date', 'remaining_cycles', + 'next_send_date_client', ]; protected $casts = [ @@ -153,6 +154,43 @@ class RecurringExpense extends BaseModel } } + + public function nextSendDateClient() :?Carbon + { + if (!$this->next_send_date) { + return null; + } + + switch ($this->frequency_id) { + case RecurringInvoice::FREQUENCY_DAILY: + return Carbon::parse($this->next_send_date)->startOfDay()->addDay(); + case RecurringInvoice::FREQUENCY_WEEKLY: + return Carbon::parse($this->next_send_date)->startOfDay()->addWeek(); + case RecurringInvoice::FREQUENCY_TWO_WEEKS: + return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2); + case RecurringInvoice::FREQUENCY_FOUR_WEEKS: + return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4); + case RecurringInvoice::FREQUENCY_MONTHLY: + return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow(); + case RecurringInvoice::FREQUENCY_TWO_MONTHS: + return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2); + case RecurringInvoice::FREQUENCY_THREE_MONTHS: + return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3); + case RecurringInvoice::FREQUENCY_FOUR_MONTHS: + return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4); + case RecurringInvoice::FREQUENCY_SIX_MONTHS: + return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6); + case RecurringInvoice::FREQUENCY_ANNUALLY: + return Carbon::parse($this->next_send_date)->startOfDay()->addYear(); + case RecurringInvoice::FREQUENCY_TWO_YEARS: + return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2); + case RecurringInvoice::FREQUENCY_THREE_YEARS: + return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3); + default: + return null; + } + } + public function remainingCycles() : int { if ($this->remaining_cycles == 0) { diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index b3138f7b29..8d6852c7c7 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -108,6 +108,7 @@ class RecurringInvoice extends BaseModel 'assigned_user_id', 'exchange_rate', 'vendor_id', + 'next_send_date_client', ]; protected $casts = [ @@ -224,7 +225,7 @@ class RecurringInvoice extends BaseModel public function nextSendDate() :?Carbon { - if (!$this->next_send_date) { + if (!$this->next_send_date_client) { return null; } @@ -236,7 +237,7 @@ class RecurringInvoice extends BaseModel /* 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'); + $this->next_send_date_client = now()->format('Y-m-d'); } @@ -244,39 +245,85 @@ class RecurringInvoice extends BaseModel 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 */ - if($offset < 0) - $offset += 86400; + // if($offset < 0) + // $offset += 86400; switch ($this->frequency_id) { case self::FREQUENCY_DAILY: - return Carbon::parse($this->next_send_date)->startOfDay()->addDay()->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay()->addSeconds($offset); case self::FREQUENCY_WEEKLY: - return Carbon::parse($this->next_send_date)->startOfDay()->addWeek()->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek()->addSeconds($offset); case self::FREQUENCY_TWO_WEEKS: - return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(2)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2)->addSeconds($offset); case self::FREQUENCY_FOUR_WEEKS: - return Carbon::parse($this->next_send_date)->startOfDay()->addWeeks(4)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4)->addSeconds($offset); case self::FREQUENCY_MONTHLY: - return Carbon::parse($this->next_send_date)->startOfDay()->addMonthNoOverflow()->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow()->addSeconds($offset); case self::FREQUENCY_TWO_MONTHS: - return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2)->addSeconds($offset); case self::FREQUENCY_THREE_MONTHS: - return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3)->addSeconds($offset); case self::FREQUENCY_FOUR_MONTHS: - return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4)->addSeconds($offset); case self::FREQUENCY_SIX_MONTHS: - return Carbon::parse($this->next_send_date)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6)->addSeconds($offset); case self::FREQUENCY_ANNUALLY: - return Carbon::parse($this->next_send_date)->startOfDay()->addYear()->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear()->addSeconds($offset); case self::FREQUENCY_TWO_YEARS: - return Carbon::parse($this->next_send_date)->startOfDay()->addYears(2)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2)->addSeconds($offset); case self::FREQUENCY_THREE_YEARS: - return Carbon::parse($this->next_send_date)->startOfDay()->addYears(3)->addSeconds($offset); + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3)->addSeconds($offset); default: return null; } } + public function nextSendDateClient() :?Carbon + { + if (!$this->next_send_date_client) { + return null; + } + + /* 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_client = now()->format('Y-m-d'); + + } + + switch ($this->frequency_id) { + case self::FREQUENCY_DAILY: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addDay(); + case self::FREQUENCY_WEEKLY: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeek(); + case self::FREQUENCY_TWO_WEEKS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(2); + case self::FREQUENCY_FOUR_WEEKS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addWeeks(4); + case self::FREQUENCY_MONTHLY: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthNoOverflow(); + case self::FREQUENCY_TWO_MONTHS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(2); + case self::FREQUENCY_THREE_MONTHS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(3); + case self::FREQUENCY_FOUR_MONTHS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(4); + case self::FREQUENCY_SIX_MONTHS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addMonthsNoOverflow(6); + case self::FREQUENCY_ANNUALLY: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYear(); + case self::FREQUENCY_TWO_YEARS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(2); + case self::FREQUENCY_THREE_YEARS: + return Carbon::parse($this->next_send_date_client)->startOfDay()->addYears(3); + default: + return null; + } + } + public function nextDateByFrequency($date) { $offset = $this->client->timezone_offset(); @@ -461,11 +508,11 @@ class RecurringInvoice extends BaseModel $data = []; - if (!Carbon::parse($this->next_send_date)) { + if (!Carbon::parse($this->next_send_date_client)) { return $data; } - $next_send_date = Carbon::parse($this->next_send_date)->copy(); + $next_send_date = Carbon::parse($this->next_send_date_client)->copy(); for ($x=0; $x<$iterations; $x++) { // we don't add the days... we calc the day of the month!! diff --git a/app/Services/Recurring/RecurringService.php b/app/Services/Recurring/RecurringService.php index 338f09f5ec..3174ba4780 100644 --- a/app/Services/Recurring/RecurringService.php +++ b/app/Services/Recurring/RecurringService.php @@ -106,6 +106,12 @@ class RecurringService $this->stop(); } + if(isset($this->recurring_entity->client)) + { + $offset = $this->recurring_entity->client->timezone_offset(); + $this->recurring_entity->next_send_date = Carbon::parse($this->recurring_entity->next_send_date_client)->startOfDay()->addSeconds($offset); + } + return $this; } diff --git a/app/Transformers/RecurringExpenseTransformer.php b/app/Transformers/RecurringExpenseTransformer.php index 2836887bea..b5632e8f9f 100644 --- a/app/Transformers/RecurringExpenseTransformer.php +++ b/app/Transformers/RecurringExpenseTransformer.php @@ -100,7 +100,8 @@ class RecurringExpenseTransformer extends EntityTransformer 'frequency_id' => (string) $recurring_expense->frequency_id, 'remaining_cycles' => (int) $recurring_expense->remaining_cycles, 'last_sent_date' => $recurring_expense->last_sent_date ?: '', - 'next_send_date' => $recurring_expense->next_send_date ?: '', + // 'next_send_date' => $recurring_expense->next_send_date ?: '', + 'next_send_date' => $recurring_expense->next_send_date_client ?: '', 'recurring_dates' => (array) [], ]; diff --git a/app/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php index 25f1388d53..feb855ddaa 100644 --- a/app/Transformers/RecurringInvoiceTransformer.php +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -95,7 +95,8 @@ class RecurringInvoiceTransformer extends EntityTransformer 'po_number' => $invoice->po_number ?: '', 'date' => $invoice->date ?: '', 'last_sent_date' => $invoice->last_sent_date ?: '', - 'next_send_date' => $invoice->next_send_date ?: '', + // 'next_send_date' => $invoice->next_send_date ?: '', + 'next_send_date' => $invoice->next_send_date_client ?: '', 'due_date' => $invoice->due_date ?: '', 'terms' => $invoice->terms ?: '', 'public_notes' => $invoice->public_notes ?: '', diff --git a/database/migrations/2022_06_01_215859_set_recurring_client_timestamp.php b/database/migrations/2022_06_01_215859_set_recurring_client_timestamp.php new file mode 100644 index 0000000000..6b261300c7 --- /dev/null +++ b/database/migrations/2022_06_01_215859_set_recurring_client_timestamp.php @@ -0,0 +1,47 @@ +datetime('next_send_date_client')->nullable(); + }); + + Schema::table('recurring_expenses', function (Blueprint $table) { + $table->datetime('next_send_date_client')->nullable(); + }); + + + RecurringInvoice::whereNotNull('next_send_date')->cursor()->each(function ($recurring_invoice){ + $recurring_invoice->next_send_date_client = $recurring_invoice->next_send_date; + $recurring_invoice->saveQuietly(); + }); + + RecurringExpense::whereNotNull('next_send_date')->cursor()->each(function ($recurring_expense){ + $recurring_expense->next_send_date_client = $recurring_expense->next_send_date; + $recurring_expense->saveQuietly(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/tests/Feature/RecurringInvoiceTest.php b/tests/Feature/RecurringInvoiceTest.php index c989415b51..225cc1939d 100644 --- a/tests/Feature/RecurringInvoiceTest.php +++ b/tests/Feature/RecurringInvoiceTest.php @@ -50,6 +50,46 @@ class RecurringInvoiceTest extends TestCase $this->makeTestData(); } + public function testTimezoneNextSendDateCalculations() + { + + $settings = $this->company->settings; + $settings->timezone_id = '112'; + $this->company->settings = $settings; + $this->company->save(); + + $data = [ + 'frequency_id' => 1, + 'status_id' => 1, + 'discount' => 0, + 'is_amount_discount' => 1, + 'po_number' => '3434343', + 'public_notes' => 'notes', + 'next_send_date' => now()->addDay()->format('Y-m-d'), + 'is_deleted' => 0, + 'custom_value1' => 0, + 'custom_value2' => 0, + 'custom_value3' => 0, + 'custom_value4' => 0, + 'status' => 1, + 'client_id' => $this->encodePrimaryKey($this->client->id), + 'line_items' => $this->buildLineItems(), + 'remaining_cycles' => -1, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/recurring_invoices?start=true', $data) + ->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals(RecurringInvoice::STATUS_ACTIVE, $arr['data']['status_id']); + + $this->assertEquals(now()->addDay()->format('Y-m-d'), $arr['data']['next_send_date']); + } + public function testPostRecurringInvoice() { $data = [