From bae82b56c152f6cf4e1e1e18a261867e1243aef0 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 14 Sep 2020 21:11:46 +1000 Subject: [PATCH 1/2] Add license --- app/Models/RecurringInvoice.php | 38 ++++++++++- .../RecurringInvoiceTransformer.php | 2 +- app/Utils/Traits/Recurring/HasRecurrence.php | 64 +++++++++++++++++++ ...40557_add_is_public_to_documents_table.php | 2 +- tests/Feature/CancelInvoiceTest.php | 10 ++- tests/Feature/ClientApiTest.php | 10 ++- tests/Feature/ClientModelTest.php | 10 ++- tests/Feature/ClientPresenterTest.php | 10 ++- tests/Feature/ClientTest.php | 10 ++- tests/Feature/CompanyGatewayApiTest.php | 10 ++- tests/Feature/CompanyGatewayTest.php | 10 ++- tests/Feature/CompanySettingsTest.php | 10 ++- tests/Feature/CompanyTest.php | 10 ++- tests/Feature/CompanyTokenApiTest.php | 10 ++- tests/Feature/CreditTest.php | 10 ++- tests/Feature/DesignApiTest.php | 10 ++- tests/Feature/GroupSettingTest.php | 10 ++- tests/Feature/InvitationTest.php | 10 ++- tests/Feature/InvoiceEmailTest.php | 10 ++- tests/Feature/InvoiceTest.php | 10 ++- tests/Feature/LoginTest.php | 10 ++- tests/Feature/MigrationTest.php | 10 ++- tests/Feature/PaymentTermsApiTest.php | 10 ++- tests/Feature/PaymentTest.php | 10 ++- tests/Feature/PdfMaker/ExampleDesign.php | 10 ++- .../PdfMaker/ExampleIntegrationTest.php | 10 ++- tests/Feature/PdfMaker/PdfMakerTest.php | 10 ++- tests/Feature/ProductTest.php | 10 ++- tests/Feature/QuoteTest.php | 10 ++- tests/Feature/RecurringInvoiceTest.php | 10 ++- tests/Feature/RecurringInvoicesCronTest.php | 10 ++- tests/Feature/RecurringQuoteTest.php | 10 ++- tests/Feature/RefundTest.php | 10 ++- tests/Feature/ReminderTest.php | 10 ++- tests/Feature/ReverseInvoiceTest.php | 10 ++- tests/Feature/Shop/ShopInvoiceTest.php | 10 ++- tests/Feature/SystemLogApiTest.php | 10 ++- tests/Feature/UpdateExchangeRatesTest.php | 10 ++- tests/Feature/UserTest.php | 10 ++- tests/Feature/WebhookAPITest.php | 10 ++- tests/Integration/CheckCacheTest.php | 10 ++- .../CheckLockedInvoiceValidationTest.php | 10 ++- tests/Integration/CheckRemindersTest.php | 10 ++- tests/Integration/CompanyLedgerTest.php | 10 ++- tests/Integration/ContainerTest.php | 10 ++- .../DownloadHistoricalInvoiceTest.php | 10 ++- tests/Integration/HtmlGenerationTest.php | 10 ++- tests/Integration/InvoiceUploadTest.php | 10 ++- tests/Integration/MarkInvoicePaidTest.php | 10 ++- tests/Integration/MultiDBUserTest.php | 10 ++- .../PaymentDrivers/AuthorizeTest.php | 10 ++- tests/Integration/SendFailedEmailsTest.php | 10 ++- tests/Integration/SystemHealthTest.php | 10 ++- tests/Integration/UniqueEmailTest.php | 10 ++- tests/Integration/UpdateCompanyLedgerTest.php | 10 ++- tests/Integration/UpdateCompanyUserTest.php | 10 ++- tests/Integration/UploadFileTest.php | 10 ++- tests/Integration/UploadLogoTest.php | 10 ++- tests/Pdf/PdfGenerationTest.php | 10 ++- tests/Unit/BaseSettingsTest.php | 10 ++- tests/Unit/CloneQuoteToInvoiceFactoryTest.php | 10 ++- tests/Unit/CollectionMergingTest.php | 10 ++- tests/Unit/CompanyDocumentsTest.php | 10 ++- tests/Unit/CompanySettingsSaveableTest.php | 10 ++- tests/Unit/CompanySettingsTest.php | 10 ++- tests/Unit/CompareCollectionTest.php | 10 ++- tests/Unit/CompareObjectTest.php | 10 ++- tests/Unit/CurrencyApiTest.php | 10 ++- tests/Unit/EncryptionSettingsTest.php | 10 ++- tests/Unit/EntityTest.php | 10 ++- tests/Unit/EvaluateStringTest.php | 10 ++- tests/Unit/FactoryCreationTest.php | 10 ++- tests/Unit/GeneratesCounterTest.php | 10 ++- tests/Unit/GoogleAnalyticsTest.php | 10 ++- tests/Unit/GroupSettingsTest.php | 10 ++- tests/Unit/GroupTest.php | 10 ++- tests/Unit/InvitationTest.php | 10 ++- tests/Unit/InvoiceActionsTest.php | 10 ++- tests/Unit/InvoiceInclusiveTest.php | 10 ++- tests/Unit/InvoiceItemInclusiveTest.php | 10 ++- tests/Unit/InvoiceItemTest.php | 10 ++- tests/Unit/InvoiceItemV2Test.php | 10 ++- tests/Unit/InvoiceTest.php | 10 ++- tests/Unit/MakesDatesTest.php | 10 ++- tests/Unit/MakesInvoiceValuesTest.php | 10 ++- tests/Unit/Migration/FeesAndLimitsTest.php | 9 +++ tests/Unit/Migration/ImportTest.php | 10 ++- tests/Unit/NestedCollectionTest.php | 10 ++- tests/Unit/NumberTest.php | 10 ++- tests/Unit/PdfVariablesTest.php | 10 ++- tests/Unit/PrimaryKeyTransformationTest.php | 10 ++- tests/Unit/RecurringDatesTest.php | 12 +++- tests/Unit/RecurringDueDatesTest.php | 42 ++++++++++++ tests/Unit/SentryTest.php | 10 ++- tests/Unit/Shop/ShopProfileTest.php | 10 ++- tests/Unit/SystemHealthTest.php | 10 ++- tests/Unit/TranslationTest.php | 10 ++- tests/Unit/UBLInvoiceTest.php | 10 ++- 98 files changed, 983 insertions(+), 96 deletions(-) create mode 100644 app/Utils/Traits/Recurring/HasRecurrence.php create mode 100644 tests/Unit/RecurringDueDatesTest.php diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 128b704fda9a..063cbe46daea 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -353,7 +353,7 @@ class RecurringInvoice extends BaseModel */ public function recurringDates() { - 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 || @@ -404,4 +404,40 @@ class RecurringInvoice extends BaseModel } + private function calculateDueDate($terms, $date) + { + + switch ($terms) { + case 'client_terms': + return $this->calculateDateFromTerms($date); + break; + case 'first_of_month': + return $this->calculateFirstDayOfMonth($date); + break; + case 'last_of_month': + break; + default: + return $this->setDayOfMonth($date, $terms); + break; + } + } + + /** + * Calculates a date based on the client payment terms. + * + * @param Carbon $date A given date + * @return NULL|Carbon The date + */ + public function calculateDateFromTerms($date) + { + + $client_payment_terms = $this->client->getSetting('payment_terms'); + + if($client_payment_terms == '')//no due date! return null; + return null; + + return $date->copy()->addDays($client_payment_terms); //add the number of days in the payment terms to the date + } + + } diff --git a/app/Transformers/RecurringInvoiceTransformer.php b/app/Transformers/RecurringInvoiceTransformer.php index 1458595b8792..e8af2751ef00 100644 --- a/app/Transformers/RecurringInvoiceTransformer.php +++ b/app/Transformers/RecurringInvoiceTransformer.php @@ -131,7 +131,7 @@ class RecurringInvoiceTransformer extends EntityTransformer 'custom_surcharge_tax3' => (bool) $invoice->custom_surcharge_tax3, 'custom_surcharge_tax4' => (bool) $invoice->custom_surcharge_tax4, 'line_items' => $invoice->line_items ?: (array) [], - 'entity_type' => 'recurring_invoice', + 'entity_type' => 'recurringInvoice', 'frequency_id' => (string) $invoice->frequency_id, 'remaining_cycles' => (int) $invoice->remaining_cycles, 'recurring_dates' => (array) $invoice->recurringDates(), diff --git a/app/Utils/Traits/Recurring/HasRecurrence.php b/app/Utils/Traits/Recurring/HasRecurrence.php new file mode 100644 index 000000000000..30f7d331a896 --- /dev/null +++ b/app/Utils/Traits/Recurring/HasRecurrence.php @@ -0,0 +1,64 @@ +copy()->startOfMonth()->addMonth(); + } + + /** + * Calculates the last day of the month. + * + * If it is the last day of the month - we add a month on. + * + * @param Carbon $date The start date + * @return Carbon The last day of month + */ + public function calculateLastDayOfMonth($date) + { + if($date->isLastOfMonth()) + return $date->copy()->endOfMonth()->addMonthNoOverflow(); + + return $date->copy()->endOfMonth(); + } + + /** + * Sets the day of the month, if in the past we ADD a month + * + * @param Carbon $date The start date + * @param String|Int $day_of_month The day of the month + */ + public function setDateOfMonth($date, $day_of_month) + { + + $set_date = $date->copy()->setUnitNoOverflow('day', $day_of_month, 'month'); + + if($set_date->isPast()) + return $set_date->addMonthNoOverflow(); + + return $set_date; + } + +} \ No newline at end of file diff --git a/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php b/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php index e7e86ec690fb..8713851f8e66 100644 --- a/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php +++ b/database/migrations/2020_08_18_140557_add_is_public_to_documents_table.php @@ -70,7 +70,7 @@ class AddIsPublicToDocumentsTable extends Migration Schema::table('recurring_invoices', function (Blueprint $table) { $table->integer('remaining_cycles')->nullable()->change(); $table->dropColumn('start_date'); - $table->integer('due_date_days')->nullable(); + $table->string('due_date_days')->nullable(); $table->date('partial_due_date')->nullable(); }); } diff --git a/tests/Feature/CancelInvoiceTest.php b/tests/Feature/CancelInvoiceTest.php index 89667fb75739..654f165a8560 100644 --- a/tests/Feature/CancelInvoiceTest.php +++ b/tests/Feature/CancelInvoiceTest.php @@ -1,5 +1,13 @@ 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->due_date_days = '5'; $recurring_invoice->next_send_date = now(); $recurring_invoice->save(); diff --git a/tests/Unit/RecurringDueDatesTest.php b/tests/Unit/RecurringDueDatesTest.php new file mode 100644 index 000000000000..6196f360b7e2 --- /dev/null +++ b/tests/Unit/RecurringDueDatesTest.php @@ -0,0 +1,42 @@ +calculateFirstDayOfMonth($date); + + $this->assertEquals('2020-03-01', $due_date->format('Y-m-d')); + + } +} diff --git a/tests/Unit/SentryTest.php b/tests/Unit/SentryTest.php index 9b8fa2ef3103..42abb51ced57 100644 --- a/tests/Unit/SentryTest.php +++ b/tests/Unit/SentryTest.php @@ -1,5 +1,13 @@ Date: Mon, 14 Sep 2020 22:13:15 +1000 Subject: [PATCH 2/2] Recurring dates --- app/Models/RecurringInvoice.php | 18 ++- app/Utils/Traits/Recurring/HasRecurrence.php | 15 +- tests/Unit/RecurringDueDatesTest.php | 141 ++++++++++++++++++- 3 files changed, 156 insertions(+), 18 deletions(-) diff --git a/app/Models/RecurringInvoice.php b/app/Models/RecurringInvoice.php index 063cbe46daea..a650ba3c7ea4 100644 --- a/app/Models/RecurringInvoice.php +++ b/app/Models/RecurringInvoice.php @@ -16,6 +16,7 @@ use App\Helpers\Invoice\InvoiceSumInclusive; use App\Models\Filterable; use App\Utils\Traits\MakesDates; use App\Utils\Traits\MakesHash; +use App\Utils\Traits\Recurring\HasRecurrence; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; @@ -29,6 +30,8 @@ class RecurringInvoice extends BaseModel use SoftDeletes; use Filterable; use MakesDates; + use HasRecurrence; + /** * Invoice Statuses. */ @@ -387,7 +390,7 @@ class RecurringInvoice extends BaseModel 'due_date' => $next_due_date->format('Y-m-d'), ]; - $next_send_date = $this->nextDateByFrequency($next_send_date); + $next_send_date = $this->calculateDueDate($next_send_date); } @@ -404,20 +407,15 @@ class RecurringInvoice extends BaseModel } - private function calculateDueDate($terms, $date) + private function calculateDueDate($date) { - switch ($terms) { - case 'client_terms': + switch ($this->due_date_days) { + case 'terms': return $this->calculateDateFromTerms($date); break; - case 'first_of_month': - return $this->calculateFirstDayOfMonth($date); - break; - case 'last_of_month': - break; default: - return $this->setDayOfMonth($date, $terms); + return $this->setDayOfMonth($date, $this->due_date_days); break; } } diff --git a/app/Utils/Traits/Recurring/HasRecurrence.php b/app/Utils/Traits/Recurring/HasRecurrence.php index 30f7d331a896..6560e18dd956 100644 --- a/app/Utils/Traits/Recurring/HasRecurrence.php +++ b/app/Utils/Traits/Recurring/HasRecurrence.php @@ -12,6 +12,7 @@ namespace App\Utils\Traits\Recurring; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Log; trait HasRecurrence { @@ -39,7 +40,7 @@ trait HasRecurrence public function calculateLastDayOfMonth($date) { if($date->isLastOfMonth()) - return $date->copy()->endOfMonth()->addMonthNoOverflow(); + return $date->copy()->addMonthNoOverflow()->endOfMonth(); return $date->copy()->endOfMonth(); } @@ -50,13 +51,19 @@ trait HasRecurrence * @param Carbon $date The start date * @param String|Int $day_of_month The day of the month */ - public function setDateOfMonth($date, $day_of_month) + public function setDayOfMonth($date, $day_of_month) { $set_date = $date->copy()->setUnitNoOverflow('day', $day_of_month, 'month'); - if($set_date->isPast()) - return $set_date->addMonthNoOverflow(); + //If the set date is less than the original date we need to add a month. + //If we are overflowing dates, then we need to diff the dates and ensure it doesn't equal 0 + if($set_date->lte($date) || $set_date->diffInDays($date) == 0) + $set_date->addMonthNoOverflow(); + + if($day_of_month == '31') + $set_date->endOfMonth(); + return $set_date; } diff --git a/tests/Unit/RecurringDueDatesTest.php b/tests/Unit/RecurringDueDatesTest.php index 6196f360b7e2..fa153791a076 100644 --- a/tests/Unit/RecurringDueDatesTest.php +++ b/tests/Unit/RecurringDueDatesTest.php @@ -24,10 +24,6 @@ class RecurringDueDatesTest extends TestCase use HasRecurrence; - public function setUp() :void - { - - } public function testFirstDate() { @@ -37,6 +33,143 @@ class RecurringDueDatesTest extends TestCase $due_date = $this->calculateFirstDayOfMonth($date); $this->assertEquals('2020-03-01', $due_date->format('Y-m-d')); + } + + public function testFirstOfMonthOnFirst() + { + + $date = Carbon::parse('2020-02-01'); + + $due_date = $this->calculateFirstDayOfMonth($date); + + $this->assertEquals('2020-03-01', $due_date->format('Y-m-d')); + + } + + + public function testFirstOfMonthOnLast() + { + + $date = Carbon::parse('2020-03-31'); + + $due_date = $this->calculateFirstDayOfMonth($date); + + $this->assertEquals('2020-04-01', $due_date->format('Y-m-d')); + + } + + public function testLastOfMonth() + { + + $date = Carbon::parse('2020-02-15'); + + $due_date = $this->calculateLastDayOfMonth($date); + + $this->assertEquals('2020-02-29', $due_date->format('Y-m-d')); + + } + + public function testLastOfMonthOnFirst() + { + + $date = Carbon::parse('2020-02-1'); + + $due_date = $this->calculateLastDayOfMonth($date); + + $this->assertEquals('2020-02-29', $due_date->format('Y-m-d')); + + } + + public function testLastOfMonthOnLast() + { + + $date = Carbon::parse('2020-02-29'); + + $due_date = $this->calculateLastDayOfMonth($date); + + $this->assertEquals('2020-03-31', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonth() + { + $date = Carbon::parse('2020-02-01'); + + $due_date = $this->setDayOfMonth($date, '15'); + + $this->assertEquals('2020-02-15', $due_date->format('Y-m-d')); } + + public function testDayOfMonthInFuture() + { + $date = Carbon::parse('2020-02-16'); + + $due_date = $this->setDayOfMonth($date, '15'); + + $this->assertEquals('2020-03-15', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonthSameDay() + { + $date = Carbon::parse('2020-02-01'); + + $due_date = $this->setDayOfMonth($date, '1'); + + $this->assertEquals('2020-03-01', $due_date->format('Y-m-d')); + + } + + + public function testDayOfMonthWithOverflow() + { + $date = Carbon::parse('2020-1-31'); + + $due_date = $this->setDayOfMonth($date, '31'); + + $this->assertEquals('2020-02-29', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonthWithOverflow2() + { + $date = Carbon::parse('2020-02-29'); + + $due_date = $this->setDayOfMonth($date, '31'); + + $this->assertEquals('2020-03-31', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonthWithOverflow3() + { + $date = Carbon::parse('2020-01-30'); + + $due_date = $this->setDayOfMonth($date, '30'); + + $this->assertEquals('2020-02-29', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonthWithOverflow4() + { + $date = Carbon::parse('2019-02-28'); + + $due_date = $this->setDayOfMonth($date, '31'); + + $this->assertEquals('2019-03-31', $due_date->format('Y-m-d')); + + } + + public function testDayOfMonthWithOverflow5() + { + $date = Carbon::parse('2019-1-31'); + + $due_date = $this->setDayOfMonth($date, '31'); + + $this->assertEquals('2019-02-28', $due_date->format('Y-m-d')); + + } + }