mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Recurring Dates
This commit is contained in:
parent
2b877d3414
commit
9c7dc24bd2
@ -49,7 +49,6 @@ class InvoiceToRecurringInvoiceFactory
|
||||
$recurring_invoice->client_id = $invoice->client_id;
|
||||
$recurring_invoice->company_id = $invoice->company_id;
|
||||
$recurring_invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$recurring_invoice->start_date = null;
|
||||
$recurring_invoice->last_sent_date = null;
|
||||
$recurring_invoice->next_send_date = null;
|
||||
$recurring_invoice->remaining_cycles = 0;
|
||||
|
@ -48,7 +48,6 @@ class RecurringInvoiceFactory
|
||||
$invoice->user_id = $user_id;
|
||||
$invoice->company_id = $company_id;
|
||||
$invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$invoice->start_date = null;
|
||||
$invoice->last_sent_date = null;
|
||||
$invoice->next_send_date = null;
|
||||
$invoice->remaining_cycles = 0;
|
||||
|
@ -47,7 +47,6 @@ class RecurringQuoteFactory
|
||||
$quote->user_id = $user_id;
|
||||
$quote->company_id = $company_id;
|
||||
$quote->frequency_id = RecurringQuote::FREQUENCY_MONTHLY;
|
||||
$quote->start_date = null;
|
||||
$quote->last_sent_date = null;
|
||||
$quote->next_send_date = null;
|
||||
$quote->remaining_cycles = 0;
|
||||
|
@ -101,5 +101,6 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
|
||||
public function messages()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Helpers\Invoice\InvoiceSumInclusive;
|
||||
use App\Models\Filterable;
|
||||
use App\Utils\Traits\MakesDates;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
@ -74,6 +76,7 @@ class RecurringInvoice extends BaseModel
|
||||
'po_number',
|
||||
'date',
|
||||
'due_date',
|
||||
'due_date_days',
|
||||
'line_items',
|
||||
'settings',
|
||||
'footer',
|
||||
@ -93,7 +96,6 @@ class RecurringInvoice extends BaseModel
|
||||
'amount',
|
||||
'partial',
|
||||
'frequency_id',
|
||||
'start_date',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
@ -176,7 +178,7 @@ class RecurringInvoice extends BaseModel
|
||||
|
||||
public function getStatusAttribute()
|
||||
{
|
||||
if ($this->status_id == self::STATUS_ACTIVE && $this->start_date > Carbon::now()) { //marked as active, but yet to fire first cycle
|
||||
if ($this->status_id == self::STATUS_ACTIVE && $this->next_send_date > Carbon::now()) { //marked as active, but yet to fire first cycle
|
||||
return self::STATUS_PENDING;
|
||||
} elseif ($this->status_id == self::STATUS_ACTIVE && $this->next_send_date > Carbon::now()) {
|
||||
return self::STATUS_COMPLETED;
|
||||
@ -215,6 +217,38 @@ class RecurringInvoice extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function nextDateByFrequency($date)
|
||||
{
|
||||
|
||||
switch ($this->frequency_id) {
|
||||
case self::FREQUENCY_WEEKLY:
|
||||
return Carbon::parse($date->addWeek());
|
||||
case self::FREQUENCY_TWO_WEEKS:
|
||||
return Carbon::parse($date->addWeeks(2));
|
||||
case self::FREQUENCY_FOUR_WEEKS:
|
||||
return Carbon::parse($date->addWeeks(4));
|
||||
case self::FREQUENCY_MONTHLY:
|
||||
return Carbon::parse($date->addMonthNoOverflow());
|
||||
case self::FREQUENCY_TWO_MONTHS:
|
||||
return Carbon::parse($date->addMonthsNoOverflow(2));
|
||||
case self::FREQUENCY_THREE_MONTHS:
|
||||
return Carbon::parse($date->addMonthsNoOverflow(3));
|
||||
case self::FREQUENCY_FOUR_MONTHS:
|
||||
return Carbon::parse($date->addMonthsNoOverflow(4));
|
||||
case self::FREQUENCY_SIX_MONTHS:
|
||||
return Carbon::parse($date->addMonthsNoOverflow(6));
|
||||
case self::FREQUENCY_ANNUALLY:
|
||||
return Carbon::parse($date->addYear());
|
||||
case self::FREQUENCY_TWO_YEARS:
|
||||
return Carbon::parse($date->addYears(2));
|
||||
case self::FREQUENCY_THREE_YEARS:
|
||||
return Carbon::parse($date->addYears(3));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function remainingCycles() : int
|
||||
{
|
||||
if ($this->remaining_cycles == 0) {
|
||||
@ -299,15 +333,33 @@ class RecurringInvoice extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
public function calc()
|
||||
{
|
||||
$invoice_calc = null;
|
||||
|
||||
if ($this->uses_inclusive_taxes) {
|
||||
$invoice_calc = new InvoiceSumInclusive($this);
|
||||
} else {
|
||||
$invoice_calc = new InvoiceSum($this);
|
||||
}
|
||||
|
||||
return $invoice_calc->build();
|
||||
}
|
||||
|
||||
/*
|
||||
* Important to note when playing with carbon dates - in order
|
||||
* not to modify the original instance, always use a `->copy()`
|
||||
*
|
||||
*/
|
||||
public function recurringDates()
|
||||
{
|
||||
//todo send back a list of the next send dates and due dates
|
||||
|
||||
info($this->next_send_date);
|
||||
/* Return early if nothing to send back! */
|
||||
if( $this->status_id == self::STATUS_COMPLETED ||
|
||||
$this->status_id == self::STATUS_DRAFT ||
|
||||
$this->status_id == self::STATUS_CANCELLED ||
|
||||
$this->remaining_cycles == 0) {
|
||||
$this->remaining_cycles == 0 ||
|
||||
!$this->next_send_date) {
|
||||
|
||||
return [];
|
||||
}
|
||||
@ -318,26 +370,37 @@ class RecurringInvoice extends BaseModel
|
||||
if($this->remaining_cycles == -1)
|
||||
$iterations = 10;
|
||||
|
||||
$next_send_date = $this->next_send_date;
|
||||
$due_date_days = $this->due_date_days;
|
||||
$data = [];
|
||||
|
||||
if(!$due_date_days)
|
||||
$due_date_days = 1;
|
||||
$next_send_date = Carbon::parse($this->next_send_date)->copy();
|
||||
|
||||
$data = [];
|
||||
for($x=0; $x<$iterations; $x++)
|
||||
{
|
||||
|
||||
for($x=0; $x<$iterations; $x++)
|
||||
{
|
||||
$next_due_date = $next_send_date->copy()->addDays($this->due_date_days);
|
||||
|
||||
$data[] = [
|
||||
'next_send_date' => $next_send_date->format('Y-m-d'),
|
||||
'due_date' => $next_send_date->addDays($due_date_days)->format('Y-m-d')
|
||||
];
|
||||
$next_send_date = Carbon::parse($next_send_date);
|
||||
$next_due_date = Carbon::parse($next_due_date);
|
||||
|
||||
}
|
||||
$data[] = [
|
||||
'next_send_date' => $next_send_date->format('Y-m-d'),
|
||||
'due_date' => $next_due_date->format('Y-m-d'),
|
||||
];
|
||||
|
||||
$next_send_date = $this->nextDateByFrequency($next_send_date);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/*If no due date is set - unset the due_date value */
|
||||
if(!$this->due_date_days || $this->due_date_days == 0){
|
||||
|
||||
foreach($data as $key => $value)
|
||||
$data[$key]['due_date'] = '';
|
||||
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -77,7 +77,7 @@ class RecurringQuote extends BaseModel
|
||||
'custom_value4',
|
||||
'amount',
|
||||
'frequency_id',
|
||||
'start_date',
|
||||
'due_date_days',
|
||||
];
|
||||
|
||||
protected $touches = [];
|
||||
|
@ -133,8 +133,8 @@ class RecurringInvoiceTransformer extends EntityTransformer
|
||||
'line_items' => $invoice->line_items ?: (array) [],
|
||||
'entity_type' => 'recurring_invoice',
|
||||
'frequency_id' => (string) $invoice->frequency_id,
|
||||
'start_date' => $invoice->start_date ?: '',
|
||||
'remaining_cycles' => (int) $invoice->remaining_cycles,
|
||||
'recurring_dates' => (array) $invoice->recurringDates(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,6 @@ class RecurringQuoteTransformer extends EntityTransformer
|
||||
'custom_text_value2' => $quote->custom_text_value2 ?: '',
|
||||
'settings' => $quote->settings ?: '',
|
||||
'frequency_id' => (int) $quote->frequency_id,
|
||||
'start_date' => $quote->start_date ?: '',
|
||||
'last_sent_date' => $quote->last_sent_date ?: '',
|
||||
'next_send_date' => $quote->next_send_date ?: '',
|
||||
'remaining_cycles' => (int) $quote->remaining_cycles,
|
||||
|
@ -25,9 +25,8 @@ $factory->define(App\Models\RecurringInvoice::class, function (Faker $faker) {
|
||||
'due_date' => $faker->date(),
|
||||
'line_items' => false,
|
||||
'frequency_id' => App\Models\RecurringInvoice::FREQUENCY_MONTHLY,
|
||||
'start_date' => $faker->date(),
|
||||
'last_sent_date' => $faker->date(),
|
||||
'next_send_date' => $faker->date(),
|
||||
'last_sent_date' => now()->subMonth(),
|
||||
'next_send_date' => now()->addMonthNoOverflow(),
|
||||
'remaining_cycles' => $faker->numberBetween(1, 10),
|
||||
'amount' => $faker->randomFloat(2, $min = 1, $max = 1000), // 48.8932
|
||||
|
||||
|
@ -71,6 +71,7 @@ class AddIsPublicToDocumentsTable extends Migration
|
||||
$table->integer('remaining_cycles')->nullable()->change();
|
||||
$table->dropColumn('start_date');
|
||||
$table->integer('due_date_days')->nullable();
|
||||
$table->date('partial_due_date')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -317,7 +317,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
@ -327,7 +327,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now()->addMinutes(2);
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
@ -337,7 +337,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now()->addMinutes(10);
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
@ -347,7 +347,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now()->addMinutes(15);
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
@ -357,7 +357,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now()->addMinutes(20);
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
@ -367,7 +367,7 @@ trait MockAccountData
|
||||
$recurring_invoice->next_send_date = Carbon::now()->addDays(10);
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_ACTIVE;
|
||||
$recurring_invoice->remaining_cycles = 2;
|
||||
$recurring_invoice->start_date = Carbon::now();
|
||||
$recurring_invoice->next_send_date = Carbon::now();
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->number = $this->getNextInvoiceNumber($this->invoice->client);
|
||||
|
87
tests/Unit/RecurringDatesTest.php
Normal file
87
tests/Unit/RecurringDatesTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Factory\CloneQuoteToInvoiceFactory;
|
||||
use App\Factory\InvoiceFactory;
|
||||
use App\Factory\InvoiceItemFactory;
|
||||
use App\Factory\RecurringInvoiceFactory;
|
||||
use App\Helpers\Invoice\InvoiceSum;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\RecurringInvoice;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
class RecurringDatesTest extends TestCase
|
||||
{
|
||||
use MockAccountData;
|
||||
use DatabaseTransactions;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
}
|
||||
|
||||
public function testRecurringDatesDraftInvoice()
|
||||
{
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->line_items = $this->buildLineItems();
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->calc()->getInvoice();
|
||||
|
||||
$this->assertEquals(0, count($recurring_invoice->recurringDates()));
|
||||
|
||||
}
|
||||
|
||||
public function testRecurringDatesPendingInvoice()
|
||||
{
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->line_items = $this->buildLineItems();
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_PENDING;
|
||||
$recurring_invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$recurring_invoice->remaining_cycles = 5;
|
||||
$recurring_invoice->due_date_days = 5;
|
||||
$recurring_invoice->next_send_date = now();
|
||||
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->calc()->getInvoice();
|
||||
|
||||
$this->assertEquals(5, count($recurring_invoice->recurringDates()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testRecurringDatesPendingInvoiceWithNoDueDate()
|
||||
{
|
||||
|
||||
$recurring_invoice = RecurringInvoiceFactory::create($this->company->id, $this->user->id);
|
||||
$recurring_invoice->line_items = $this->buildLineItems();
|
||||
$recurring_invoice->client_id = $this->client->id;
|
||||
|
||||
$recurring_invoice->status_id = RecurringInvoice::STATUS_PENDING;
|
||||
$recurring_invoice->frequency_id = RecurringInvoice::FREQUENCY_MONTHLY;
|
||||
$recurring_invoice->remaining_cycles = 5;
|
||||
$recurring_invoice->due_date_days = null;
|
||||
$recurring_invoice->next_send_date = now();
|
||||
|
||||
$recurring_invoice->save();
|
||||
|
||||
$recurring_invoice->calc()->getInvoice();
|
||||
|
||||
$this->assertEquals(5, count($recurring_invoice->recurringDates()));
|
||||
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user