Merge pull request #11024 from ctrl-f5/feat/improve-date-handling

improved balance range date handling
This commit is contained in:
James Cole
2025-10-08 06:30:56 +02:00
committed by GitHub
10 changed files with 51 additions and 43 deletions

View File

@@ -84,7 +84,7 @@ class AccountController extends Controller
$data = $request->getData(); $data = $request->getData();
$types = $data['types']; $types = $data['types'];
$query = $data['query']; $query = $data['query'];
$date = $data['date'] ?? today(config('app.timezone')); $date = $data['date'];
$return = []; $return = [];
$timer = Timer::getInstance(); $timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query)); $timer->start(sprintf('AC accounts "%s"', $query));

View File

@@ -589,8 +589,6 @@ class BasicController extends Controller
private function getNetWorthInfo(Carbon $end): array private function getNetWorthInfo(Carbon $end): array
{ {
$end->endOfDay();
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s'))); Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s')));

View File

@@ -48,10 +48,11 @@ class AutocompleteRequest extends FormRequest
// remove 'initial balance' from allowed types. its internal // remove 'initial balance' from allowed types. its internal
$array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]); $array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]);
$date = $this->getCarbonDate('date') ?? today(config('app.timezone'));
return [ return [
'types' => $array, 'types' => $array,
'query' => $this->convertString('query'), 'query' => $this->convertString('query'),
'date' => $this->getCarbonDate('date'), 'date' => $date->endOfDay(),
]; ];
} }

View File

@@ -28,6 +28,7 @@ use FireflyIII\Exceptions\ValidationException;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
/** /**
* Request class for end points that require date parameters. * Request class for end points that require date parameters.
@@ -55,7 +56,6 @@ class DateRequest extends FormRequest
// sanity check on dates: // sanity check on dates:
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
$start->startOfDay(); $start->startOfDay();
$end->endOfDay(); $end->endOfDay();
if ($start->diffInYears($end, true) > 5) { if ($start->diffInYears($end, true) > 5) {

View File

@@ -91,14 +91,12 @@ class IndexController extends Controller
/** @var Carbon $end */ /** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth()); $end = clone session('end', today(config('app.timezone'))->endOfMonth());
// #10618 go to the end of the previous day.
$start->subSecond();
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); [
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); $startBalances,
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); $endBalances,
] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids); $activities = Steam::getLastActivities($ids);
@@ -169,14 +167,12 @@ class IndexController extends Controller
/** @var Carbon $end */ /** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth()); $end = clone session('end', today(config('app.timezone'))->endOfMonth());
// #10618 go to the end of the previous day.
$start->subSecond();
$ids = $accounts->pluck('id')->toArray(); $ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); Log::debug(sprintf('index: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); [
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); $startBalances,
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); $endBalances,
] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
$activities = Steam::getLastActivities($ids); $activities = Steam::getLastActivities($ids);

View File

@@ -116,10 +116,11 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts); $accountNames = $this->extractNames($accounts);
// grab all balances // grab all balances
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); Log::debug(sprintf('expenseAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); [
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); $startBalances,
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); $endBalances,
] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
Log::debug('Done collecting balances'); Log::debug('Done collecting balances');
// loop the accounts, then check for balance and currency info. // loop the accounts, then check for balance and currency info.
foreach ($accounts as $account) { foreach ($accounts as $account) {
@@ -666,10 +667,11 @@ class AccountController extends Controller
$accountNames = $this->extractNames($accounts); $accountNames = $this->extractNames($accounts);
// grab all balances // grab all balances
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); Log::debug(sprintf('revAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); [
$startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); $startBalances,
$endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); $endBalances,
] = Steam::accountsBalancesInRange($start, $end, $accounts, $this->primaryCurrency, $this->convertToPrimary);
// loop the accounts, then check for balance and currency info. // loop the accounts, then check for balance and currency info.

View File

@@ -50,10 +50,11 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$yesterday = clone $start; $yesterday = clone $start;
$yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including. $yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
$end->endOfDay(); // needs to be end of day to be correct. $end->endOfDay(); // needs to be end of day to be correct.
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s'))); Log::debug(sprintf('getAccountReport: accountsBalancesInRange("%s", "%s")', $yesterday->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); [
$startSet = Steam::accountsBalancesOptimized($accounts, $yesterday); $startSet,
$endSet = Steam::accountsBalancesOptimized($accounts, $end); $endSet,
] = Steam::accountsBalancesInRange($yesterday, $end, $accounts);
Log::debug('Start of accountreport'); Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */ /** @var AccountRepositoryInterface $repository */

View File

@@ -289,8 +289,10 @@ class AccountEnrichment implements EnrichmentInterface
{ {
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary); $this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
if ($this->start instanceof Carbon && $this->end instanceof Carbon) { if ($this->start instanceof Carbon && $this->end instanceof Carbon) {
$this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary); [
$this->endBalances = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary); $this->startBalances,
$this->endBalances,
] = Steam::accountsBalancesInRange($this->start, $this->end, $this->collection, $this->primaryCurrency, $this->convertToPrimary);
} }
} }

View File

@@ -402,21 +402,21 @@ trait ConvertsDataTypes
*/ */
protected function getCarbonDate(string $field): ?Carbon protected function getCarbonDate(string $field): ?Carbon
{ {
$result = null; $data = (string)$this->get($field);
Log::debug(sprintf('Date string is "%s"', $data));
Log::debug(sprintf('Date string is "%s"', (string)$this->get($field))); if ('' === $data) {
return null;
}
try { try {
$result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null; return new Carbon($data, config('app.timezone'));
} catch (InvalidFormatException) { } catch (InvalidFormatException) {
// @ignoreException // @ignoreException
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); Log::debug(sprintf('Exception when parsing date "%s".', $data));
}
if (!$result instanceof Carbon) {
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
} }
return $result; return null;
} }
/** /**

View File

@@ -49,7 +49,7 @@ use function Safe\preg_replace;
*/ */
class Steam class Steam
{ {
public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array
{ {
Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String())); Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
$result = []; $result = [];
@@ -61,7 +61,7 @@ class Steam
$arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) ->where('transaction_journals.date', $inclusive ? '<=': '<', $date->format('Y-m-d H:i:s'))
->groupBy(['transactions.account_id', 'transaction_currencies.code']) ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray() ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
; ;
@@ -125,6 +125,14 @@ class Steam
return $result; return $result;
} }
public function accountsBalancesInRange(Carbon $start, Carbon $end, Collection $accounts, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
{
return [
$this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false),
$this->accountsBalancesOptimized($accounts, $end, $primary, $convertToPrimary),
];
}
/** /**
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers * https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
*/ */