diff --git a/app/Support/Models/BillDateCalculator.php b/app/Support/Models/BillDateCalculator.php new file mode 100644 index 0000000000..d511a1e77e --- /dev/null +++ b/app/Support/Models/BillDateCalculator.php @@ -0,0 +1,139 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Models; + +use Carbon\Carbon; +use FireflyIII\Models\Bill; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; + +class BillDateCalculator +{ + /** + * Returns the dates a bill needs to be paid. + * + * @param Carbon $earliest + * @param Carbon $latest + * @param Carbon $billStart + * @param Carbon|null $lastPaid + * + * @return array + */ + public function getPayDates(Carbon $earliest, Carbon $latest, Carbon $billStart, string $period, int $skip, ?Carbon $lastPaid): array + { + Log::debug('Now in BillDateCalculator::getPayDates()'); + Log::debug(sprintf('Dates must be between %s and %s.', $earliest->format('Y-m-d'), $latest->format('Y-m-d'))); + Log::debug(sprintf('Bill started on %s, period is "%s", skip is %d, last paid = "%s".', $billStart->format('Y-m-d'), $period, $skip, $lastPaid?->format('Y-m-d'))); + + + $set = new Collection(); + $currentStart = clone $earliest; + + // 2023-06-23 subDay to fix 7655 + $currentStart->subDay(); + $loop = 0; + Log::debug('Start of loop'); + while ($currentStart <= $latest) { + Log::debug(sprintf('Current start is %s', $currentStart->format('Y-m-d'))); + $nextExpectedMatch = $this->nextDateMatch(clone $currentStart, clone $billStart, $period, $skip); + Log::debug(sprintf('Next expected match is %s', $nextExpectedMatch->format('Y-m-d'))); + + // If nextExpectedMatch is after end, we stop looking: + if ($nextExpectedMatch->gt($latest)) { + Log::debug('Next expected match is after $latest.'); + if ($set->count() > 0) { + Log::debug(sprintf('Already have %d date(s), so we can safely break.', $set->count())); + break; + } + Log::debug('Add date to set anyway, since we had no dates yet.'); + $set->push(clone $nextExpectedMatch); + continue; + } + + // add to set, if the date is ON or after the start parameter + // AND date is after last paid date + if ( + $nextExpectedMatch->gte($earliest) // date is after "earliest possible date" + && (null === $lastPaid || $nextExpectedMatch->gt($lastPaid)) // date is after last paid date, if that date is not NULL + ) { + Log::debug('Add date to set, because it is after earliest possible date and after last paid date.'); + $set->push(clone $nextExpectedMatch); + } + + // 2023-10 + // for the next loop, go to end of period, THEN add day. + $nextExpectedMatch->addDay(); + $currentStart = clone $nextExpectedMatch; + + $loop++; + if ($loop > 12) { + Log::debug('Loop is more than 12, so we break.'); + break; + } + } + Log::debug('end of loop'); + $simple = $set->map( + static function (Carbon $date) { + return $date->format('Y-m-d'); + } + ); + Log::debug(sprintf('Found %d pay dates', $set->count()), $simple->toArray()); + + return $simple->toArray(); + + } + + /** + * Given a bill and a date, this method will tell you at which moment this bill expects its next + * transaction given the earliest date this could happen. + * + * That date must be AFTER $billStartDate, as a sanity check. + * + * @param Carbon $earliest + * @param Carbon $billStartDate + * @param string $period + * @param int $skip + * + * @return Carbon + */ + protected function nextDateMatch(Carbon $earliest, Carbon $billStartDate, string $period, int $skip): Carbon + { + Log::debug(sprintf('Bill start date is %s', $billStartDate->format('Y-m-d'))); + if ($earliest->lt($billStartDate)) { + Log::debug('Earliest possible date is after bill start, so just return bill start date.'); + return $billStartDate; + } + + $steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate); + $result = clone $billStartDate; + if ($steps > 0) { + $steps -= 1; + Log::debug(sprintf('Steps is %d, because addPeriod already adds 1.', $steps)); + $result = app('navigation')->addPeriod($billStartDate, $period, $steps); + } + Log::debug(sprintf('Number of steps is %d, added to %s, result is %s', $steps, $billStartDate->format('Y-m-d'), $result->format('Y-m-d'))); + return $result; + } + +} diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index da56e4fa53..cd431bee09 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -29,6 +29,7 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Support\Models\BillDateCalculator; use Illuminate\Support\Collection; /** @@ -37,6 +38,7 @@ use Illuminate\Support\Collection; class BillTransformer extends AbstractTransformer { private BillRepositoryInterface $repository; + private BillDateCalculator $calculator; /** * BillTransformer constructor. @@ -46,6 +48,7 @@ class BillTransformer extends AbstractTransformer public function __construct() { $this->repository = app(BillRepositoryInterface::class); + $this->calculator = app(BillDateCalculator::class); } /** @@ -59,7 +62,8 @@ class BillTransformer extends AbstractTransformer { $paidData = $this->paidData($bill); $lastPaidDate = $this->getLastPaidDate($paidData); - $payDates = $this->payDates($bill, $lastPaidDate); + //$payDates = $this->payDates($bill, $lastPaidDate); + $payDates = $this->calculator->getPayDates($this->parameters->get('start'), $this->parameters->get('end'), $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate); $currency = $bill->transactionCurrency; $notes = $this->repository->getNoteText($bill); $notes = '' === $notes ? null : $notes; @@ -78,7 +82,7 @@ class BillTransformer extends AbstractTransformer $paidDataFormatted = []; $payDatesFormatted = []; - foreach ($paidData['paid_dates'] as $object) { + foreach ($paidData as $object) { $object['date'] = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'))->toAtomString(); $paidDataFormatted[] = $object; } @@ -167,10 +171,7 @@ class BillTransformer extends AbstractTransformer if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) { app('log')->debug('parameters are NULL, return empty array'); - return [ - 'paid_dates' => [], - 'next_expected_match' => null, - ]; + return []; } // 2023-07-1 sub one day from the start date to fix a possible bug (see #7704) // 2023-07-18 this particular date is used to search for the last paid date. @@ -205,12 +206,11 @@ class BillTransformer extends AbstractTransformer 'transaction_group_id' => (string)$entry->transaction_group_id, 'transaction_journal_id' => (string)$entry->id, 'date' => $entry->date->format('Y-m-d'), + 'date_object' => $entry->date, ]; } - return [ - 'paid_dates' => $result, - ]; + return $result; } /** @@ -246,16 +246,18 @@ class BillTransformer extends AbstractTransformer { app('log')->debug('getLastPaidDate()'); $return = null; - foreach ($paidData['paid_dates'] as $entry) { + foreach ($paidData as $entry) { if (null !== $return) { - $current = Carbon::createFromFormat('!Y-m-d', $entry['date'], config('app.timezone')); + /** @var Carbon $current */ + $current = $entry['date_object']; if ($current->gt($return)) { $return = clone $current; } app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); } if (null === $return) { - $return = Carbon::createFromFormat('!Y-m-d', $entry['date'], config('app.timezone')); + /** @var Carbon $return */ + $return = $entry['date_object']; app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); } } diff --git a/composer.lock b/composer.lock index 48858e9949..c1e7b66242 100644 --- a/composer.lock +++ b/composer.lock @@ -1939,16 +1939,16 @@ }, { "name": "laravel/framework", - "version": "v10.32.1", + "version": "v10.33.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b30e44f20d244f7ba125283e14a8bbac167f4e5b" + "reference": "4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b30e44f20d244f7ba125283e14a8bbac167f4e5b", - "reference": "b30e44f20d244f7ba125283e14a8bbac167f4e5b", + "url": "https://api.github.com/repos/laravel/framework/zipball/4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8", + "reference": "4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8", "shasum": "" }, "require": { @@ -2137,7 +2137,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-11-14T22:57:08+00:00" + "time": "2023-11-21T14:49:31+00:00" }, { "name": "laravel/passport", @@ -2529,34 +2529,34 @@ }, { "name": "lcobucci/clock", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc" + "reference": "6f28b826ea01306b07980cb8320ab30b966cd715" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/30a854ceb22bd87d83a7a4563b3f6312453945fc", - "reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/6f28b826ea01306b07980cb8320ab30b966cd715", + "reference": "6f28b826ea01306b07980cb8320ab30b966cd715", "shasum": "" }, "require": { - "php": "~8.2.0", + "php": "~8.2.0 || ~8.3.0", "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" }, "require-dev": { - "infection/infection": "^0.26", - "lcobucci/coding-standard": "^10.0.0", - "phpstan/extension-installer": "^1.2", - "phpstan/phpstan": "^1.10.7", + "infection/infection": "^0.27", + "lcobucci/coding-standard": "^11.0.0", + "phpstan/extension-installer": "^1.3.1", + "phpstan/phpstan": "^1.10.25", "phpstan/phpstan-deprecation-rules": "^1.1.3", - "phpstan/phpstan-phpunit": "^1.3.10", - "phpstan/phpstan-strict-rules": "^1.5.0", - "phpunit/phpunit": "^10.0.17" + "phpstan/phpstan-phpunit": "^1.3.13", + "phpstan/phpstan-strict-rules": "^1.5.1", + "phpunit/phpunit": "^10.2.3" }, "type": "library", "autoload": { @@ -2577,7 +2577,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/3.1.0" + "source": "https://github.com/lcobucci/clock/tree/3.2.0" }, "funding": [ { @@ -2589,25 +2589,23 @@ "type": "patreon" } ], - "time": "2023-03-20T19:12:25+00:00" + "time": "2023-11-17T17:00:27+00:00" }, { "name": "lcobucci/jwt", - "version": "5.1.0", + "version": "5.2.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908" + "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/f0031c07b96db6a0ca649206e7eacddb7e9d5908", - "reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211", + "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211", "shasum": "" }, "require": { - "ext-hash": "*", - "ext-json": "*", "ext-openssl": "*", "ext-sodium": "*", "php": "~8.1.0 || ~8.2.0 || ~8.3.0", @@ -2652,7 +2650,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.1.0" + "source": "https://github.com/lcobucci/jwt/tree/5.2.0" }, "funding": [ { @@ -2664,7 +2662,7 @@ "type": "patreon" } ], - "time": "2023-10-31T06:41:47+00:00" + "time": "2023-11-20T21:17:42+00:00" }, { "name": "league/commonmark", @@ -2998,16 +2996,16 @@ }, { "name": "league/flysystem", - "version": "3.19.0", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "1b2aa10f2326e0351399b8ce68e287d8e9209a83" + "reference": "a326d8a2d007e4ca327a57470846e34363789258" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b2aa10f2326e0351399b8ce68e287d8e9209a83", - "reference": "1b2aa10f2326e0351399b8ce68e287d8e9209a83", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a326d8a2d007e4ca327a57470846e34363789258", + "reference": "a326d8a2d007e4ca327a57470846e34363789258", "shasum": "" }, "require": { @@ -3072,7 +3070,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.19.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.21.0" }, "funding": [ { @@ -3084,20 +3082,20 @@ "type": "github" } ], - "time": "2023-11-07T09:04:28+00:00" + "time": "2023-11-18T13:59:15+00:00" }, { "name": "league/flysystem-local", - "version": "3.19.0", + "version": "3.21.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "8d868217f9eeb4e9a7320db5ccad825e9a7a4076" + "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/8d868217f9eeb4e9a7320db5ccad825e9a7a4076", - "reference": "8d868217f9eeb4e9a7320db5ccad825e9a7a4076", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/470eb1c09eaabd49ebd908ae06f23983ba3ecfe7", + "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7", "shasum": "" }, "require": { @@ -3132,7 +3130,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.19.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.21.0" }, "funding": [ { @@ -3144,7 +3142,7 @@ "type": "github" } ], - "time": "2023-11-06T20:35:28+00:00" + "time": "2023-11-18T13:41:42+00:00" }, { "name": "league/fractal", @@ -6105,7 +6103,7 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", @@ -6152,7 +6150,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" }, "funding": [ { @@ -6326,7 +6324,7 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", @@ -6382,7 +6380,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.0" }, "funding": [ { @@ -6558,16 +6556,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" + "reference": "1ee70e699b41909c209a0c930f11034b93578654" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", - "reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654", + "reference": "1ee70e699b41909c209a0c930f11034b93578654", "shasum": "" }, "require": { @@ -6616,7 +6614,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.4.0" }, "funding": [ { @@ -6632,7 +6630,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/http-foundation", @@ -8030,16 +8028,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", - "reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838", + "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838", "shasum": "" }, "require": { @@ -8092,7 +8090,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.0" }, "funding": [ { @@ -8108,7 +8106,7 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2023-07-30T20:28:31+00:00" }, { "name": "symfony/string", @@ -8293,16 +8291,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.3.0", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", - "reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5", + "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5", "shasum": "" }, "require": { @@ -8351,7 +8349,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0" }, "funding": [ { @@ -8367,7 +8365,7 @@ "type": "tidelift" } ], - "time": "2023-05-30T17:17:10+00:00" + "time": "2023-07-25T15:08:44+00:00" }, { "name": "symfony/uid", @@ -8644,26 +8642,27 @@ }, { "name": "twig/twig", - "version": "v3.7.1", + "version": "v3.8.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", - "reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", + "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d", "shasum": "" }, "require": { "php": ">=7.2.5", "symfony/polyfill-ctype": "^1.8", - "symfony/polyfill-mbstring": "^1.3" + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" }, "require-dev": { "psr/container": "^1.0|^2.0", - "symfony/phpunit-bridge": "^5.4.9|^6.3" + "symfony/phpunit-bridge": "^5.4.9|^6.3|^7.0" }, "type": "library", "autoload": { @@ -8699,7 +8698,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.7.1" + "source": "https://github.com/twigphp/Twig/tree/v3.8.0" }, "funding": [ { @@ -8711,7 +8710,7 @@ "type": "tidelift" } ], - "time": "2023-08-28T11:09:02+00:00" + "time": "2023-11-21T18:54:41+00:00" }, { "name": "vlucas/phpdotenv", @@ -10063,16 +10062,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.24.2", + "version": "1.24.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "bcad8d995980440892759db0c32acae7c8e79442" + "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", - "reference": "bcad8d995980440892759db0c32acae7c8e79442", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/12f01d214f1c73b9c91fdb3b1c415e4c70652083", + "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083", "shasum": "" }, "require": { @@ -10104,22 +10103,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.2" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.24.3" }, - "time": "2023-09-26T12:28:12+00:00" + "time": "2023-11-18T20:15:32+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.43", + "version": "1.10.44", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "2c4129f6ca8c7cfa870098884b8869b410a5a361" + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/2c4129f6ca8c7cfa870098884b8869b410a5a361", - "reference": "2c4129f6ca8c7cfa870098884b8869b410a5a361", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b", + "reference": "bf84367c53a23f759513985c54ffe0d0c249825b", "shasum": "" }, "require": { @@ -10168,7 +10167,7 @@ "type": "tidelift" } ], - "time": "2023-11-19T19:55:25+00:00" + "time": "2023-11-21T16:30:46+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -10269,16 +10268,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.7", + "version": "10.1.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e" + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", - "reference": "355324ca4980b8916c18b9db29f3ef484078f26e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735", + "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735", "shasum": "" }, "require": { @@ -10335,7 +10334,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.9" }, "funding": [ { @@ -10343,7 +10342,7 @@ "type": "github" } ], - "time": "2023-10-04T15:34:17+00:00" + "time": "2023-11-23T12:23:20+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11661,16 +11660,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -11699,7 +11698,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -11707,7 +11706,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" } ], "aliases": [], diff --git a/tests/integration/CreatesApplication.php b/tests/integration/CreatesApplication.php index 6dbc977700..b4ae59a714 100644 --- a/tests/integration/CreatesApplication.php +++ b/tests/integration/CreatesApplication.php @@ -38,7 +38,7 @@ trait CreatesApplication */ public function createApplication() { - $app = require_once __DIR__ . '/../../bootstrap/app.php'; + $app = require __DIR__ . '/../../bootstrap/app.php'; $app->make(Kernel::class)->bootstrap(); diff --git a/tests/integration/Support/Models/BillDateCalculatorTest.php b/tests/integration/Support/Models/BillDateCalculatorTest.php new file mode 100644 index 0000000000..f4a3c66c79 --- /dev/null +++ b/tests/integration/Support/Models/BillDateCalculatorTest.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\integration\Support\Models; + +use Carbon\Carbon; +use FireflyIII\Support\Models\BillDateCalculator; +use Tests\integration\TestCase; + +/** + * Class BillDateCalculatorTest + */ +class BillDateCalculatorTest extends TestCase +{ + private BillDateCalculator $calculator; + + public function __construct(string $name) + { + parent::__construct($name); + $this->calculator = new BillDateCalculator(); + } + + public static function provideDates(): iterable + { + // Carbon $earliest, Carbon $latest, Carbon $billStart, string $period, int $skip, ?Carbon $lastPaid + return [ + // basic monthly bill. + '1M' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => null, 'expected' => ['2023-11-01']], + ]; + } + + /** + * Stupid long method names I'm not going to do that. + * + * @dataProvider provideDates + */ + public function testGivenSomeDataItWorks(Carbon $earliest, Carbon $latest, Carbon $billStart, string $period, int $skip, ?Carbon $lastPaid, array $expected): void + { + $result = $this->calculator->getPayDates($earliest, $latest, $billStart, $period, $skip, $lastPaid); + self::assertSame($expected, $result); + } +} diff --git a/tests/unit/Support/Calendar/CalculatorTest.php b/tests/unit/Support/Calendar/CalculatorTest.php index afff0e9986..fb8a8c381d 100644 --- a/tests/unit/Support/Calendar/CalculatorTest.php +++ b/tests/unit/Support/Calendar/CalculatorTest.php @@ -28,7 +28,6 @@ namespace Tests\unit\Support\Calendar; use FireflyIII\Exceptions\IntervalException; use FireflyIII\Support\Calendar\Calculator; use FireflyIII\Support\Calendar\Periodicity; -use Generator; use PHPUnit\Framework\TestCase; use Tests\unit\Support\Calendar\Periodicity\BimonthlyTest; use Tests\unit\Support\Calendar\Periodicity\DailyTest; diff --git a/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php b/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php index f1a909ff42..8b8ae95e5c 100644 --- a/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php +++ b/tests/unit/Support/Calendar/Periodicity/IntervalTestCase.php @@ -26,7 +26,6 @@ declare(strict_types=1); namespace Tests\unit\Support\Calendar\Periodicity; use FireflyIII\Support\Calendar\Periodicity\Interval; -use Generator; use PHPUnit\Framework\TestCase; abstract class IntervalTestCase extends TestCase diff --git a/tests/unit/Support/NavigationEndOfPeriodTest.php b/tests/unit/Support/NavigationEndOfPeriodTest.php index 42a71ace03..4412689a4f 100644 --- a/tests/unit/Support/NavigationEndOfPeriodTest.php +++ b/tests/unit/Support/NavigationEndOfPeriodTest.php @@ -43,6 +43,9 @@ class NavigationEndOfPeriodTest extends TestCase $this->navigation = new Navigation(); } + /** + * @return iterable + */ public static function provideDates(): iterable { return [