Add test case for bill dates

This commit is contained in:
James Cole 2023-11-26 07:19:57 +01:00
parent 5e49e149b1
commit a6c355c7b8
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
8 changed files with 310 additions and 107 deletions

View File

@ -0,0 +1,139 @@
<?php
/*
* BillDateCalculator.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -29,6 +29,7 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\ObjectGroup; use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\Models\BillDateCalculator;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
/** /**
@ -37,6 +38,7 @@ use Illuminate\Support\Collection;
class BillTransformer extends AbstractTransformer class BillTransformer extends AbstractTransformer
{ {
private BillRepositoryInterface $repository; private BillRepositoryInterface $repository;
private BillDateCalculator $calculator;
/** /**
* BillTransformer constructor. * BillTransformer constructor.
@ -46,6 +48,7 @@ class BillTransformer extends AbstractTransformer
public function __construct() public function __construct()
{ {
$this->repository = app(BillRepositoryInterface::class); $this->repository = app(BillRepositoryInterface::class);
$this->calculator = app(BillDateCalculator::class);
} }
/** /**
@ -59,7 +62,8 @@ class BillTransformer extends AbstractTransformer
{ {
$paidData = $this->paidData($bill); $paidData = $this->paidData($bill);
$lastPaidDate = $this->getLastPaidDate($paidData); $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; $currency = $bill->transactionCurrency;
$notes = $this->repository->getNoteText($bill); $notes = $this->repository->getNoteText($bill);
$notes = '' === $notes ? null : $notes; $notes = '' === $notes ? null : $notes;
@ -78,7 +82,7 @@ class BillTransformer extends AbstractTransformer
$paidDataFormatted = []; $paidDataFormatted = [];
$payDatesFormatted = []; $payDatesFormatted = [];
foreach ($paidData['paid_dates'] as $object) { foreach ($paidData as $object) {
$object['date'] = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'))->toAtomString(); $object['date'] = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'))->toAtomString();
$paidDataFormatted[] = $object; $paidDataFormatted[] = $object;
} }
@ -167,10 +171,7 @@ class BillTransformer extends AbstractTransformer
if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) { if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) {
app('log')->debug('parameters are NULL, return empty array'); app('log')->debug('parameters are NULL, return empty array');
return [ return [];
'paid_dates' => [],
'next_expected_match' => null,
];
} }
// 2023-07-1 sub one day from the start date to fix a possible bug (see #7704) // 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. // 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_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string)$entry->id, 'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->format('Y-m-d'), 'date' => $entry->date->format('Y-m-d'),
'date_object' => $entry->date,
]; ];
} }
return [ return $result;
'paid_dates' => $result,
];
} }
/** /**
@ -246,16 +246,18 @@ class BillTransformer extends AbstractTransformer
{ {
app('log')->debug('getLastPaidDate()'); app('log')->debug('getLastPaidDate()');
$return = null; $return = null;
foreach ($paidData['paid_dates'] as $entry) { foreach ($paidData as $entry) {
if (null !== $return) { 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)) { if ($current->gt($return)) {
$return = clone $current; $return = clone $current;
} }
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d'))); app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
} }
if (null === $return) { 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'))); app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
} }
} }

183
composer.lock generated
View File

@ -1939,16 +1939,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v10.32.1", "version": "v10.33.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "b30e44f20d244f7ba125283e14a8bbac167f4e5b" "reference": "4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/b30e44f20d244f7ba125283e14a8bbac167f4e5b", "url": "https://api.github.com/repos/laravel/framework/zipball/4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8",
"reference": "b30e44f20d244f7ba125283e14a8bbac167f4e5b", "reference": "4536872e3e5b6be51b1f655dafd12c9a4fa0cfe8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2137,7 +2137,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "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", "name": "laravel/passport",
@ -2529,34 +2529,34 @@
}, },
{ {
"name": "lcobucci/clock", "name": "lcobucci/clock",
"version": "3.1.0", "version": "3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/lcobucci/clock.git", "url": "https://github.com/lcobucci/clock.git",
"reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc" "reference": "6f28b826ea01306b07980cb8320ab30b966cd715"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/lcobucci/clock/zipball/30a854ceb22bd87d83a7a4563b3f6312453945fc", "url": "https://api.github.com/repos/lcobucci/clock/zipball/6f28b826ea01306b07980cb8320ab30b966cd715",
"reference": "30a854ceb22bd87d83a7a4563b3f6312453945fc", "reference": "6f28b826ea01306b07980cb8320ab30b966cd715",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "~8.2.0", "php": "~8.2.0 || ~8.3.0",
"psr/clock": "^1.0" "psr/clock": "^1.0"
}, },
"provide": { "provide": {
"psr/clock-implementation": "1.0" "psr/clock-implementation": "1.0"
}, },
"require-dev": { "require-dev": {
"infection/infection": "^0.26", "infection/infection": "^0.27",
"lcobucci/coding-standard": "^10.0.0", "lcobucci/coding-standard": "^11.0.0",
"phpstan/extension-installer": "^1.2", "phpstan/extension-installer": "^1.3.1",
"phpstan/phpstan": "^1.10.7", "phpstan/phpstan": "^1.10.25",
"phpstan/phpstan-deprecation-rules": "^1.1.3", "phpstan/phpstan-deprecation-rules": "^1.1.3",
"phpstan/phpstan-phpunit": "^1.3.10", "phpstan/phpstan-phpunit": "^1.3.13",
"phpstan/phpstan-strict-rules": "^1.5.0", "phpstan/phpstan-strict-rules": "^1.5.1",
"phpunit/phpunit": "^10.0.17" "phpunit/phpunit": "^10.2.3"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -2577,7 +2577,7 @@
"description": "Yet another clock abstraction", "description": "Yet another clock abstraction",
"support": { "support": {
"issues": "https://github.com/lcobucci/clock/issues", "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": [ "funding": [
{ {
@ -2589,25 +2589,23 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2023-03-20T19:12:25+00:00" "time": "2023-11-17T17:00:27+00:00"
}, },
{ {
"name": "lcobucci/jwt", "name": "lcobucci/jwt",
"version": "5.1.0", "version": "5.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/lcobucci/jwt.git", "url": "https://github.com/lcobucci/jwt.git",
"reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908" "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/lcobucci/jwt/zipball/f0031c07b96db6a0ca649206e7eacddb7e9d5908", "url": "https://api.github.com/repos/lcobucci/jwt/zipball/0ba88aed12c04bd2ed9924f500673f32b67a6211",
"reference": "f0031c07b96db6a0ca649206e7eacddb7e9d5908", "reference": "0ba88aed12c04bd2ed9924f500673f32b67a6211",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-hash": "*",
"ext-json": "*",
"ext-openssl": "*", "ext-openssl": "*",
"ext-sodium": "*", "ext-sodium": "*",
"php": "~8.1.0 || ~8.2.0 || ~8.3.0", "php": "~8.1.0 || ~8.2.0 || ~8.3.0",
@ -2652,7 +2650,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/lcobucci/jwt/issues", "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": [ "funding": [
{ {
@ -2664,7 +2662,7 @@
"type": "patreon" "type": "patreon"
} }
], ],
"time": "2023-10-31T06:41:47+00:00" "time": "2023-11-20T21:17:42+00:00"
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
@ -2998,16 +2996,16 @@
}, },
{ {
"name": "league/flysystem", "name": "league/flysystem",
"version": "3.19.0", "version": "3.21.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/flysystem.git", "url": "https://github.com/thephpleague/flysystem.git",
"reference": "1b2aa10f2326e0351399b8ce68e287d8e9209a83" "reference": "a326d8a2d007e4ca327a57470846e34363789258"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/1b2aa10f2326e0351399b8ce68e287d8e9209a83", "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a326d8a2d007e4ca327a57470846e34363789258",
"reference": "1b2aa10f2326e0351399b8ce68e287d8e9209a83", "reference": "a326d8a2d007e4ca327a57470846e34363789258",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3072,7 +3070,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/thephpleague/flysystem/issues", "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": [ "funding": [
{ {
@ -3084,20 +3082,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-11-07T09:04:28+00:00" "time": "2023-11-18T13:59:15+00:00"
}, },
{ {
"name": "league/flysystem-local", "name": "league/flysystem-local",
"version": "3.19.0", "version": "3.21.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/flysystem-local.git", "url": "https://github.com/thephpleague/flysystem-local.git",
"reference": "8d868217f9eeb4e9a7320db5ccad825e9a7a4076" "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/8d868217f9eeb4e9a7320db5ccad825e9a7a4076", "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/470eb1c09eaabd49ebd908ae06f23983ba3ecfe7",
"reference": "8d868217f9eeb4e9a7320db5ccad825e9a7a4076", "reference": "470eb1c09eaabd49ebd908ae06f23983ba3ecfe7",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -3132,7 +3130,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/thephpleague/flysystem-local/issues", "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": [ "funding": [
{ {
@ -3144,7 +3142,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-11-06T20:35:28+00:00" "time": "2023-11-18T13:41:42+00:00"
}, },
{ {
"name": "league/fractal", "name": "league/fractal",
@ -6105,7 +6103,7 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.3.0", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
@ -6152,7 +6150,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
}, },
"funding": [ "funding": [
{ {
@ -6326,7 +6324,7 @@
}, },
{ {
"name": "symfony/event-dispatcher-contracts", "name": "symfony/event-dispatcher-contracts",
"version": "v3.3.0", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git", "url": "https://github.com/symfony/event-dispatcher-contracts.git",
@ -6382,7 +6380,7 @@
"standards" "standards"
], ],
"support": { "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": [ "funding": [
{ {
@ -6558,16 +6556,16 @@
}, },
{ {
"name": "symfony/http-client-contracts", "name": "symfony/http-client-contracts",
"version": "v3.3.0", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-client-contracts.git", "url": "https://github.com/symfony/http-client-contracts.git",
"reference": "3b66325d0176b4ec826bffab57c9037d759c31fb" "reference": "1ee70e699b41909c209a0c930f11034b93578654"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/3b66325d0176b4ec826bffab57c9037d759c31fb", "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/1ee70e699b41909c209a0c930f11034b93578654",
"reference": "3b66325d0176b4ec826bffab57c9037d759c31fb", "reference": "1ee70e699b41909c209a0c930f11034b93578654",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6616,7 +6614,7 @@
"standards" "standards"
], ],
"support": { "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": [ "funding": [
{ {
@ -6632,7 +6630,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-23T14:45:45+00:00" "time": "2023-07-30T20:28:31+00:00"
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
@ -8030,16 +8028,16 @@
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.3.0", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4" "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"reference": "40da9cc13ec349d9e4966ce18b5fbcd724ab10a4", "reference": "b3313c2dbffaf71c8de2934e2ea56ed2291a3838",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8092,7 +8090,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.3.0" "source": "https://github.com/symfony/service-contracts/tree/v3.4.0"
}, },
"funding": [ "funding": [
{ {
@ -8108,7 +8106,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-23T14:45:45+00:00" "time": "2023-07-30T20:28:31+00:00"
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
@ -8293,16 +8291,16 @@
}, },
{ {
"name": "symfony/translation-contracts", "name": "symfony/translation-contracts",
"version": "v3.3.0", "version": "v3.4.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation-contracts.git", "url": "https://github.com/symfony/translation-contracts.git",
"reference": "02c24deb352fb0d79db5486c0c79905a85e37e86" "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/02c24deb352fb0d79db5486c0c79905a85e37e86", "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dee0c6e5b4c07ce851b462530088e64b255ac9c5",
"reference": "02c24deb352fb0d79db5486c0c79905a85e37e86", "reference": "dee0c6e5b4c07ce851b462530088e64b255ac9c5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -8351,7 +8349,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/translation-contracts/tree/v3.3.0" "source": "https://github.com/symfony/translation-contracts/tree/v3.4.0"
}, },
"funding": [ "funding": [
{ {
@ -8367,7 +8365,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-05-30T17:17:10+00:00" "time": "2023-07-25T15:08:44+00:00"
}, },
{ {
"name": "symfony/uid", "name": "symfony/uid",
@ -8644,26 +8642,27 @@
}, },
{ {
"name": "twig/twig", "name": "twig/twig",
"version": "v3.7.1", "version": "v3.8.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/twigphp/Twig.git", "url": "https://github.com/twigphp/Twig.git",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554" "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "url": "https://api.github.com/repos/twigphp/Twig/zipball/9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"reference": "a0ce373a0ca3bf6c64b9e3e2124aca502ba39554", "reference": "9d15f0ac07f44dc4217883ec6ae02fd555c6f71d",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8", "symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3" "symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php80": "^1.22"
}, },
"require-dev": { "require-dev": {
"psr/container": "^1.0|^2.0", "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", "type": "library",
"autoload": { "autoload": {
@ -8699,7 +8698,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/twigphp/Twig/issues", "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": [ "funding": [
{ {
@ -8711,7 +8710,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-28T11:09:02+00:00" "time": "2023-11-21T18:54:41+00:00"
}, },
{ {
"name": "vlucas/phpdotenv", "name": "vlucas/phpdotenv",
@ -10063,16 +10062,16 @@
}, },
{ {
"name": "phpstan/phpdoc-parser", "name": "phpstan/phpdoc-parser",
"version": "1.24.2", "version": "1.24.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpdoc-parser.git", "url": "https://github.com/phpstan/phpdoc-parser.git",
"reference": "bcad8d995980440892759db0c32acae7c8e79442" "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/bcad8d995980440892759db0c32acae7c8e79442", "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/12f01d214f1c73b9c91fdb3b1c415e4c70652083",
"reference": "bcad8d995980440892759db0c32acae7c8e79442", "reference": "12f01d214f1c73b9c91fdb3b1c415e4c70652083",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -10104,22 +10103,22 @@
"description": "PHPDoc parser with support for nullable, intersection and generic types", "description": "PHPDoc parser with support for nullable, intersection and generic types",
"support": { "support": {
"issues": "https://github.com/phpstan/phpdoc-parser/issues", "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", "name": "phpstan/phpstan",
"version": "1.10.43", "version": "1.10.44",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "2c4129f6ca8c7cfa870098884b8869b410a5a361" "reference": "bf84367c53a23f759513985c54ffe0d0c249825b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/2c4129f6ca8c7cfa870098884b8869b410a5a361", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/bf84367c53a23f759513985c54ffe0d0c249825b",
"reference": "2c4129f6ca8c7cfa870098884b8869b410a5a361", "reference": "bf84367c53a23f759513985c54ffe0d0c249825b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -10168,7 +10167,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-11-19T19:55:25+00:00" "time": "2023-11-21T16:30:46+00:00"
}, },
{ {
"name": "phpstan/phpstan-deprecation-rules", "name": "phpstan/phpstan-deprecation-rules",
@ -10269,16 +10268,16 @@
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
"version": "10.1.7", "version": "10.1.9",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e" "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/355324ca4980b8916c18b9db29f3ef484078f26e", "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/a56a9ab2f680246adcf3db43f38ddf1765774735",
"reference": "355324ca4980b8916c18b9db29f3ef484078f26e", "reference": "a56a9ab2f680246adcf3db43f38ddf1765774735",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -10335,7 +10334,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", "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": [ "funding": [
{ {
@ -10343,7 +10342,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-10-04T15:34:17+00:00" "time": "2023-11-23T12:23:20+00:00"
}, },
{ {
"name": "phpunit/php-file-iterator", "name": "phpunit/php-file-iterator",
@ -11661,16 +11660,16 @@
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
"version": "1.2.1", "version": "1.2.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/theseer/tokenizer.git", "url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11699,7 +11698,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": { "support": {
"issues": "https://github.com/theseer/tokenizer/issues", "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": [ "funding": [
{ {
@ -11707,7 +11706,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2021-07-28T10:34:58+00:00" "time": "2023-11-20T00:12:19+00:00"
} }
], ],
"aliases": [], "aliases": [],

View File

@ -38,7 +38,7 @@ trait CreatesApplication
*/ */
public function createApplication() public function createApplication()
{ {
$app = require_once __DIR__ . '/../../bootstrap/app.php'; $app = require __DIR__ . '/../../bootstrap/app.php';
$app->make(Kernel::class)->bootstrap(); $app->make(Kernel::class)->bootstrap();

View File

@ -0,0 +1,62 @@
<?php
/*
* BillDateCalculatorTest.php
* Copyright (c) 2023 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -28,7 +28,6 @@ namespace Tests\unit\Support\Calendar;
use FireflyIII\Exceptions\IntervalException; use FireflyIII\Exceptions\IntervalException;
use FireflyIII\Support\Calendar\Calculator; use FireflyIII\Support\Calendar\Calculator;
use FireflyIII\Support\Calendar\Periodicity; use FireflyIII\Support\Calendar\Periodicity;
use Generator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Tests\unit\Support\Calendar\Periodicity\BimonthlyTest; use Tests\unit\Support\Calendar\Periodicity\BimonthlyTest;
use Tests\unit\Support\Calendar\Periodicity\DailyTest; use Tests\unit\Support\Calendar\Periodicity\DailyTest;

View File

@ -26,7 +26,6 @@ declare(strict_types=1);
namespace Tests\unit\Support\Calendar\Periodicity; namespace Tests\unit\Support\Calendar\Periodicity;
use FireflyIII\Support\Calendar\Periodicity\Interval; use FireflyIII\Support\Calendar\Periodicity\Interval;
use Generator;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
abstract class IntervalTestCase extends TestCase abstract class IntervalTestCase extends TestCase

View File

@ -43,6 +43,9 @@ class NavigationEndOfPeriodTest extends TestCase
$this->navigation = new Navigation(); $this->navigation = new Navigation();
} }
/**
* @return iterable
*/
public static function provideDates(): iterable public static function provideDates(): iterable
{ {
return [ return [