Compare commits

..

4 Commits

Author SHA1 Message Date
James Cole
5a638ba02e Fix some code and add a changelog. 2026-02-22 20:17:01 +01:00
James Cole
cf3c836293 Merge pull request #11787 from R1DEN/main
Fix chart API balance carry-forward bug
2026-02-22 20:08:23 +01:00
James Cole
3cd3dafb7f Add backticks to IP addresses. 2026-02-22 17:45:40 +01:00
RiDEN
ed3f4f62ee Fix chart API balance carry-forward bug and add missing deleted_at filter
The chart loop used exact-date lookup into the range array, silently
dropping transactions that fell between chart boundary dates when
period > 1D. Replace with a range-walking approach that advances
through all intervening entries.

Also add whereNull('transaction_journals.deleted_at') to
accountsBalancesOptimized() to match the daily-delta query, and fix
a double-space typo in a date format string.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:37:00 +02:00
4 changed files with 40 additions and 31 deletions

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
use FireflyIII\Enums\UserRoleEnum;
@@ -51,7 +52,7 @@ class AccountController extends Controller
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private array $chartData = [];
private AccountRepositoryInterface $repository;
/**
@@ -99,28 +100,28 @@ class AccountController extends Controller
private function renderAccountData(array $params, Account $account): void
{
Log::debug(sprintf('Now in %s(array, #%d)', __METHOD__, $account->id));
$currency = $this->repository->getAccountCurrency($account);
$currentStart = clone $params['start'];
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToPrimary);
$period = $params['period'] ?? '1D';
$currency = $this->repository->getAccountCurrency($account);
$currentStart = clone $params['start'];
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToPrimary);
$period = $params['period'] ?? '1D';
$previous = array_values($range)[0]['balance'];
$pcPrevious = null;
$previous = array_values($range)[0]['balance'];
$pcPrevious = null;
if (!$currency instanceof TransactionCurrency) {
$currency = $this->primaryCurrency;
}
$currentSet = [
$currentSet = [
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string) $currency->id,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the primary currency
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_id' => (string)$this->primaryCurrency->id,
// the default currency of the user (could be the same!)
'date' => $params['start']->toAtomString(),
@@ -134,7 +135,7 @@ class AccountController extends Controller
];
if ($this->convertToPrimary) {
$currentSet['pc_entries'] = [];
$currentSet['primary_currency_id'] = (string) $this->primaryCurrency->id;
$currentSet['primary_currency_id'] = (string)$this->primaryCurrency->id;
$currentSet['primary_currency_code'] = $this->primaryCurrency->code;
$currentSet['primary_currency_symbol'] = $this->primaryCurrency->symbol;
$currentSet['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
@@ -142,23 +143,29 @@ class AccountController extends Controller
}
// create array of values to collect.
$rangeDates = array_map(static fn(string $d): Carbon => Carbon::createFromFormat('Y-m-d', $d)->startOfDay(), array_keys($range));
$rangeVals = array_values($range);
$rangeIdx = 0;
$rangeCount = count($rangeDates);
while ($currentStart <= $params['end']) {
$format = $currentStart->format('Y-m-d');
$label = $currentStart->toAtomString();
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
$previous = $balance;
$currentSet['entries'][$label] = $balance;
$label = $currentStart->toAtomString();
// do the same for the primary currency balance, if relevant:
$pcBalance = null;
if ($this->convertToPrimary) {
$pcBalance = array_key_exists($format, $range) ? $range[$format]['pc_balance'] : $pcPrevious;
$pcPrevious = $pcBalance;
$currentSet['pc_entries'][$label] = $pcBalance;
// Advance through all range entries up to current chart date
while ($rangeIdx < $rangeCount && $rangeDates[$rangeIdx] <= $currentStart) {
$previous = $rangeVals[$rangeIdx]['balance'];
if ($this->convertToPrimary) {
$pcPrevious = $rangeVals[$rangeIdx]['pc_balance'];
}
++$rangeIdx;
}
$currentStart = Navigation::addPeriod($currentStart, $period);
// $currentStart->addDay();
$currentSet['entries'][$label] = $previous;
if ($this->convertToPrimary) {
$currentSet['pc_entries'][$label] = $pcPrevious;
}
$currentStart = Navigation::addPeriod($currentStart, $period);
}
$this->chartData[] = $currentSet;
}

View File

@@ -87,6 +87,7 @@ class Steam
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s'))
->whereNull('transaction_journals.deleted_at')
->groupBy(['transactions.account_id', 'transaction_currencies.code'])
->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])
->toArray()
@@ -435,7 +436,7 @@ class Steam
if ($cache->has()) {
Log::debug('Return cached finalAccountBalanceInRange');
// return $cache->get();
return $cache->get();
}
$balances = [];
@@ -469,7 +470,7 @@ class Steam
->transactions()
->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
->groupBy('transaction_journals.date')
->groupBy('transactions.transaction_currency_id')
->orderBy('transaction_journals.date', 'ASC')

View File

@@ -23,6 +23,7 @@ And yes, despite my goal not to change things, some very clever users (that's yo
### Fixed
- [Discussion 11685](https://github.com/orgs/firefly-iii/discussions/11685) (Yearly budget best practices) started by @molnarti
- [Issue 11778](https://github.com/firefly-iii/firefly-iii/issues/11778) (API update rule trigger only accepts "store-journal") reported by @jhns-de
- #11785
- Test notification was broken for system owners.
## v6.4.23 - 2026-02-20

View File

@@ -60,13 +60,13 @@ return [
// unknown user login attempt
'unknown_user_subject' => 'An unknown user tried to log in',
'unknown_user_body' => 'An unknown user (:ip) tried to log in to Firefly III. The email address they used was ":address".',
'unknown_user_message' => 'The email address they (:ip) used was ":address".',
'unknown_user_body' => 'An unknown user (`:ip`) tried to log in to Firefly III. The email address they used was `:address`.',
'unknown_user_message' => 'The email address they (`:ip`) used was `:address`.',
// known user login attempt
'failed_login_subject' => 'Firefly III detected a failed login attempt',
'failed_login_body' => 'Firefly III detected that somebody (you?) failed to login with your account ":email". Please verify that this was you.',
'failed_login_message' => 'A failed login attempt (:ip) on your Firefly III account ":email" was detected.',
'failed_login_body' => 'Firefly III detected that somebody (you?) failed to login with your account `:email`. Please verify that this was you.',
'failed_login_message' => 'A failed login attempt (`:ip`) on your Firefly III account `:email` was detected.',
'failed_login_warning' => 'If you recognize this IP address or the login attempt, you can ignore this message. If you didn\'t try to login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!',
// registered
@@ -168,7 +168,7 @@ return [
'used_backup_code_subject' => 'You have used a back-up code to login',
'used_backup_code_slack' => 'You (:email) have used a back-up code to login',
'used_backup_code_intro' => 'You (:email) have used a back-up code to login to Firefly III. You now have one less back-up code to login with. Please remove it from your list.',
'used_backup_code_intro' => 'You (:email) have used a back-up co de to login to Firefly III. You now have one less back-up code to login with. Please remove it from your list.',
'used_backup_code_warning' => 'If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.',
// few left: