1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-09-20 08:21:34 +02:00

Fixes for timezone issues with recurring entities

This commit is contained in:
David Bomba 2022-06-02 13:49:29 +10:00
parent 38b77e72fe
commit 3bf56af37f
15 changed files with 224 additions and 23 deletions

View File

@ -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)

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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']);
}

View File

@ -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();
}

View File

@ -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();

View File

@ -667,6 +667,8 @@ class Client extends BaseModel implements HasLocalePreference
$offset -= $timezone->utc_offset;
$offset += ($entity_send_time * 3600);
nlog("offset = {$offset}");
return $offset;
}

View File

@ -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) {

View File

@ -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,34 +245,80 @@ 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;
}
@ -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!!

View File

@ -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;
}

View File

@ -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) [],
];

View File

@ -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 ?: '',

View File

@ -0,0 +1,47 @@
<?php
use App\Models\RecurringExpense;
use App\Models\RecurringInvoice;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class SetRecurringClientTimestamp extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('recurring_invoices', function (Blueprint $table) {
$table->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()
{
//
}
}

View File

@ -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 = [