Compare commits

..

19 Commits

Author SHA1 Message Date
github-actions[bot]
850c824da1 Merge pull request #11673 from firefly-iii/release-1770531358
🤖 Automatically merge the PR into the develop branch.
2026-02-08 07:16:05 +01:00
JC5
34160da67a 🤖 Auto commit for release 'develop' on 2026-02-08 2026-02-08 07:15:58 +01:00
James Cole
2848a64c13 Fix sponsorship link. 2026-02-08 07:11:24 +01:00
James Cole
998d6bf874 Fix #11668 2026-02-08 06:49:27 +01:00
James Cole
b85200e1f6 Fix #11667 2026-02-08 06:46:14 +01:00
James Cole
6944001887 Fix #11671 2026-02-08 06:22:40 +01:00
github-actions[bot]
154abf1fc0 Merge pull request #11665 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-02-07 07:55:46 +01:00
github-actions[bot]
0113cc1651 Merge pull request #11664 from firefly-iii/release-1770447333
🤖 Automatically merge the PR into the develop branch.
2026-02-07 07:55:40 +01:00
JC5
38b5b656a1 🤖 Auto commit for release 'v6.4.17' on 2026-02-07 2026-02-07 07:55:33 +01:00
github-actions[bot]
e74163a7ec Merge pull request #11663 from firefly-iii/release-1770445570
🤖 Automatically merge the PR into the develop branch.
2026-02-07 07:26:17 +01:00
JC5
c60094d231 🤖 Auto commit for release 'develop' on 2026-02-07 2026-02-07 07:26:10 +01:00
James Cole
39d46d469c Fix query parser logging. 2026-02-07 06:53:12 +01:00
github-actions[bot]
6caea5ffa3 Merge pull request #11662 from firefly-iii/release-1770442761
🤖 Automatically merge the PR into the develop branch.
2026-02-07 06:39:28 +01:00
JC5
4024f76a51 🤖 Auto commit for release 'develop' on 2026-02-07 2026-02-07 06:39:21 +01:00
James Cole
de84946371 Expand changelog. 2026-02-07 06:33:30 +01:00
James Cole
6d4aca54de Fix #11246 2026-02-07 06:32:11 +01:00
James Cole
256262b2ba Fix #11657 2026-02-07 06:16:23 +01:00
James Cole
fb035ba594 Fix #11660 2026-02-07 06:09:41 +01:00
James Cole
20776949a6 Clean up changelog. 2026-02-06 18:32:55 +01:00
37 changed files with 309 additions and 114 deletions

BIN
.github/assets/img/testmu.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -36,6 +36,7 @@ class UpdatedExistingAccount extends Event
* Create a new event instance.
*/
public function __construct(
public Account $account
public Account $account,
public array $oldData
) {}
}

View File

@@ -74,8 +74,8 @@ class IndexController extends Controller
$this->cleanupObjectGroups();
$this->repository->correctOrder();
$this->repository->correctTransfers();
$start = session('start');
$end = session('end');
$start = session('start')->clone();
$end = session('end')->clone();
$viewRange = Preferences::get('viewRange', '1M')->data;
// give the end some extra space when the user has last7, last30 or last90.

View File

@@ -194,7 +194,12 @@ class ShowController extends Controller
foreach ($group['transactions'] as $transaction) {
// add normal amount:
$symbol = $transaction['currency_symbol'];
$amounts[$symbol] ??= ['amount' => '0', 'symbol' => $symbol, 'decimal_places' => $transaction['currency_decimal_places']];
$amounts[$symbol] ??= [
'amount' => '0',
'symbol' => $symbol,
'decimal_places' => $transaction['currency_decimal_places'],
'approximate' => false,
];
$amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], (string) $transaction['amount']);
// add foreign amount:
@@ -207,6 +212,7 @@ class ShowController extends Controller
$foreignSymbol = $transaction['foreign_currency_symbol'];
$amounts[$foreignSymbol] ??= [
'amount' => '0',
'approximate' => false,
'symbol' => $foreignSymbol,
'decimal_places' => $transaction['foreign_currency_decimal_places'],
];
@@ -222,6 +228,7 @@ class ShowController extends Controller
$primarySymbol = $this->primaryCurrency->symbol;
$amounts[$primarySymbol] ??= [
'amount' => '0',
'approximate' => true,
'symbol' => $this->primaryCurrency->symbol,
'decimal_places' => $this->primaryCurrency->decimal_places,
];

View File

@@ -167,7 +167,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
/** @var BudgetLimitRepositoryInterface $repository */
$repository = app(BudgetLimitRepositoryInterface::class);
$repository->setUserGroup($autoBudget->budget->user->userGroup);
$repository->setUser($autoBudget->budget->user);
$budgetLimit = $repository->store([
'currency_id' => $autoBudget->transaction_currency_id,

View File

@@ -29,7 +29,11 @@ use FireflyIII\Events\Model\Account\UpdatedExistingAccount;
use FireflyIII\Handlers\ExchangeRate\ConversionParameters;
use FireflyIII\Handlers\ExchangeRate\ConvertsAmountToPrimaryAmount;
use FireflyIII\Models\Account;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
@@ -40,6 +44,89 @@ class UpdatesAccountInformation implements ShouldQueue
{
$this->recalculateCredit($event->account);
$this->updateVirtualBalance($event->account);
if ($event instanceof UpdatedExistingAccount) {
$this->renameRules($event->account, $event->oldData);
}
}
private function correctRuleActions(Account $account, array $oldData, Rule $rule): void
{
$fields = ['set_source_account', 'set_destination_account'];
Log::debug(sprintf('Check if rule #%d actions reference account #%d "%s"', $rule->id, $account->id, $account->name));
$fixed = 0;
/** @var RuleAction $action */
foreach ($rule->ruleActions as $action) {
// fix name:
if ($oldData['name'] === $action->action_value && in_array($action->action_type, $fields, true)) {
Log::debug(sprintf('Rule action #%d "%s" has old account name, replace with new.', $action->id, $action->action_type));
$action->action_value = $account->name;
$action->save();
++$fixed;
}
}
Log::debug(sprintf('Corrected %d action(s) for rule #%d', $fixed, $rule->id));
}
private function correctRuleTriggers(Account $account, array $oldData, Rule $rule): void
{
$nameFields = [
'source_account_is',
'source_account_contains',
'source_account_ends',
'source_account_starts',
'destination_account_is',
'destination_account_contains',
'destination_account_ends',
'destination_account_starts',
'account_is',
'account_contains',
'account_ends',
'account_starts',
];
$numberFields = [
'source_account_nr_is',
'source_account_nr_contains',
'source_account_nr_ends',
'source_account_nr_starts',
'destination_account_nr_is',
'destination_account_nr_contains',
'destination_account_nr_starts',
'account_nr_is',
'account_nr_contains',
'account_nr_ends',
'account_nr_starts',
];
Log::debug(sprintf('Check if rule #%d triggers reference account #%d "%s"', $rule->id, $account->id, $account->name));
$fixed = 0;
/** @var RuleTrigger $trigger */
foreach ($rule->ruleTriggers as $trigger) {
// fix name:
if ($oldData['name'] === $trigger->trigger_value && in_array($trigger->trigger_type, $nameFields, true)) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account name, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->name;
$trigger->save();
++$fixed;
}
// fix IBAN:
if ($oldData['iban'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account IBAN, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->iban;
$trigger->save();
++$fixed;
}
// fix account number: // account_number
if ($oldData['account_number'] === $trigger->trigger_value && in_array($trigger->trigger_type, $numberFields, true)) {
Log::debug(sprintf('Rule trigger #%d "%s" has old account account_number, replace with new.', $trigger->id, $trigger->trigger_type));
$trigger->trigger_value = $account->iban;
$trigger->save();
++$fixed;
}
}
Log::debug(sprintf('Corrected %d trigger(s) for rule #%d', $fixed, $rule->id));
}
private function recalculateCredit(Account $account): void
@@ -52,6 +139,20 @@ class UpdatesAccountInformation implements ShouldQueue
$object->recalculate();
}
private function renameRules(Account $account, array $oldData): void
{
Log::debug('Updated account, will now correct rules.');
$repository = app(RuleRepositoryInterface::class);
$repository->setUser($account->user);
$rules = $repository->getAll();
/** @var Rule $rule */
foreach ($rules as $rule) {
$this->correctRuleTriggers($account, $oldData, $rule);
$this->correctRuleActions($account, $oldData, $rule);
}
}
private function updateVirtualBalance(Account $account): void
{
Log::debug('Will updateVirtualBalance');

View File

@@ -29,6 +29,11 @@ trait SupportsGroupProcessingTrait
protected function processRules(Collection $set, string $type): void
{
Log::debug(sprintf('Will now processRules("%s") for %d journal(s)', $type, $set->count()));
if (0 === $set->count()) {
return;
}
$array = $set->pluck('id')->toArray();
/** @var TransactionJournal $first */
@@ -63,10 +68,16 @@ trait SupportsGroupProcessingTrait
return;
}
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $objects->transactionJournals->pluck('date')->sort()->first();
$fromInternalDate = $this->getFromInternalDate($objects->transactionJournals->pluck('id')->toArray());
$earliest = $fromInternalDate->lt($earliest) ? $fromInternalDate : $earliest;
if (0 === $objects->accounts->count()) {
return;
}
$earliest = today()->subDays(2);
if ($objects->transactionJournals->count() > 0) {
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $objects->transactionJournals->pluck('date')->sort()->first();
$fromInternalDate = $this->getFromInternalDate($objects->transactionJournals->pluck('id')->toArray());
$earliest = $fromInternalDate->lt($earliest) ? $fromInternalDate : $earliest;
}
Log::debug(sprintf('Found earliest date: %s', $earliest->toW3cString()));
Log::debug('Found accounts to process', $objects->accounts->pluck('id')->toArray());
@@ -78,6 +89,8 @@ trait SupportsGroupProcessingTrait
{
if (!auth()->check()) {
Log::debug('Will NOT remove period statistics for all objects, because no user detected.');
return;
}
Log::debug('Will now remove period statistics for all objects.');
@@ -122,6 +135,10 @@ trait SupportsGroupProcessingTrait
{
Log::debug(sprintf('Will now create webhook messages for %d group(s)', $groups->count()));
if (0 === $groups->count()) {
return;
}
/** @var TransactionGroup $first */
$first = $groups->first();
$user = $first->user;

View File

@@ -60,6 +60,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function categoryStartsWith(string $query, int $limit): Collection
{
Log::debug(sprintf('Find a category that starts with "%s"', $query));
$search = $this->user->categories();
if ('' !== $query) {
$search->whereLike('name', sprintf('%s%%', $query));

View File

@@ -95,6 +95,11 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
#[Override]
public function deleteStatisticsForPrefix(string $prefix, Collection $dates): void
{
if (null === $this->userGroup) {
Log::warning('No user group, so cannot continue.');
return;
}
$count = $this->userGroup
->periodStatistics()
->where(function (Builder $q) use ($dates): void {

View File

@@ -78,9 +78,11 @@ class AccountUpdateService
{
Log::debug(sprintf('Now in %s', __METHOD__));
$this->accountRepository->setUser($account->user);
$this->user = $account->user;
$account = $this->updateAccount($account, $data);
$account = $this->updateAccountOrder($account, $data);
$this->user = $account->user;
$oldData = $account->toArray();
$oldData['account_number'] = $this->accountRepository->getMetaValue($account, 'account_number');
$account = $this->updateAccount($account, $data);
$account = $this->updateAccountOrder($account, $data);
// find currency, or use default currency instead.
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
@@ -109,7 +111,7 @@ class AccountUpdateService
// update preferences if inactive:
$this->updatePreferences($account);
event(new UpdatedExistingAccount($account));
event(new UpdatedExistingAccount($account, $oldData));
return $account;
}

View File

@@ -39,6 +39,10 @@ class Calculator
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private static array $intervals = [];
public function isAvailablePeriodicity(Periodicity $periodicity): bool

View File

@@ -96,6 +96,14 @@ class ExportDataGenerator
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
public function __construct()
{
$this->accounts = new Collection();

View File

@@ -84,6 +84,10 @@ trait PeriodOverview
// temp data holder
// temp data holder
// temp data holder
// temp data holder
// temp data holder
// temp data holder
// temp data holder
private array $transactions; // temp data holder
// temp data holder
@@ -92,6 +96,14 @@ trait PeriodOverview
// temp data holder
// temp data holder
// temp data holder
// temp data holder
// temp data holder
/**
* This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for

View File

@@ -44,10 +44,18 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private readonly bool $convertToPrimary; // @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $currencies = [];
private array $currencyIds = [];
private array $ids = [];

View File

@@ -44,6 +44,10 @@ class BudgetLimitEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $currencies = [];
private array $currencyIds = [];
private Carbon $end;

View File

@@ -46,10 +46,18 @@ class PiggyBankEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $accounts = []; // @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $amounts = [];
private Collection $collection;
private array $currencies = [];

View File

@@ -41,10 +41,18 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $accountIds = []; // @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private Collection $collection;
private array $currencies = [];
private array $groupIds = [];

View File

@@ -50,6 +50,10 @@ class SubscriptionEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private readonly bool $convertToPrimary;
private ?Carbon $end = null;
private array $mappedObjects = [];

View File

@@ -61,6 +61,14 @@ class TransactionGroupEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
public function __construct()
{
$this->dateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date'];

View File

@@ -46,10 +46,18 @@ class WebhookEnrichment implements EnrichmentInterface
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $ids = []; // @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
// @phpstan-ignore-line
private array $responses = [];
private array $triggers = [];
private array $webhookDeliveries = [];

View File

@@ -57,12 +57,13 @@ class QueryParser implements QueryParserInterface
while ($this->position < $count) {
$char = $chrArray[$this->position];
$nextChar = $chrArray[$this->position + 1] ?? '';
$prevChar = $chrArray[$this->position - 1] ?? '';
// Log::debug(sprintf('Char #%d: %s', $this->position, $char));
// If we're in a quoted string, we treat all characters except another quote as ordinary characters
if ($inQuotes) {
if ('\\' === $char && '"' === $nextChar) {
// Log::debug('BACKSLASH!');
if ('\\' === $char && '"' === $nextChar && '\\' !== $prevChar) {
// Log::debug('Found a backslash and the next one is a double quote.');
// escaped quote, pretend it's a normal char and continue two places (skipping the actual character).
$tokenUnderConstruction .= '\\'.$nextChar;
$this->position += 2;

View File

@@ -3,11 +3,20 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.4.18
### Fixed
- [Discussion 11671](https://github.com/orgs/firefly-iii/discussions/11671) (Subscriptions Next Expected Match) started by @idgaron
- [Issue 11667](https://github.com/firefly-iii/firefly-iii/issues/11667) (Account names and numbers are not corrected in rules when the account is updated) reported by @Kage1
- [Issue 11668](https://github.com/firefly-iii/firefly-iii/issues/11668) (Auto-budget cron crashes on develop: Call to a member function budgets() on null (BudgetLimitRepository.php:311)) reported by @sykmer
## v6.4.17 - 2026-02-06
### Added
- Batch processing. Firefly III now has a setting (under `/settings`) that allows you to send `"batch_processing": true` with new transactions over the API. If this setting is enabled and the value is `true`, Firefly III will not fire rules, webhooks or other events untill you either send `false` with a transaction OR use the [API end point](https://api-docs.firefly-iii.org/) `/v1/api/batch/finish`. This should speed up (large) data imports. It's a little experimental, use at your own risk.
- [Issue 11614](https://github.com/firefly-iii/firefly-iii/issues/11614) (Add New Taiwan Dollar to Currency Seeder) reported by @nick322
- [Issue 11246](https://github.com/firefly-iii/firefly-iii/issues/11246) (Distinguish automatically converted amount from foreign amount) reported by @jfpedroza
### Changed
@@ -19,25 +28,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
- [PR 11589](https://github.com/firefly-iii/firefly-iii/pull/11589) (apply user-selected light/dark mode to form elements (checkboxes, date picker) #8613 #7620) reported by @mateuszkulapl
- [Issue 11399](https://github.com/firefly-iii/firefly-iii/issues/11399) (Unusual behavior in audit logs (multi-currency)) reported by @jgmm81
- [Discussion 11431](https://github.com/orgs/firefly-iii/discussions/11431) (Settings don't get saved) started by @PVTejas
- [Issue 11541](https://github.com/firefly-iii/firefly-iii/issues/11541) (Display running balance fails for transactions between accounts with different currencies) reported by @SledgehammerPL
- [Issue 11544](https://github.com/firefly-iii/firefly-iii/issues/11544) (Clean up events and handlers) reported by @JC5
- [Issue 11546](https://github.com/firefly-iii/firefly-iii/issues/11546) (Wrong invitation expiry time) reported by @GunoH
- [Discussion 11431](https://github.com/orgs/firefly-iii/discussions/11431) (Settings don't get saved) started by @PVTejas
- [Issue 11399](https://github.com/firefly-iii/firefly-iii/issues/11399) (Unusual behavior in audit logs (multi-currency)) reported by @jgmm81
- [PR 11569](https://github.com/firefly-iii/firefly-iii/pull/11569) (Fix layout overflow issues with long content in v1 and v2 layouts) reported by @gian21391
- [Issue 11563](https://github.com/firefly-iii/firefly-iii/issues/11563) (Tag Report/Insight API Endpoint for Tags Non Functional) reported by @Unsantae
- [PR 11589](https://github.com/firefly-iii/firefly-iii/pull/11589) (apply user-selected light/dark mode to form elements (checkboxes, date picker) #8613 #7620) reported by @mateuszkulapl
- [PR 11569](https://github.com/firefly-iii/firefly-iii/pull/11569) (Fix layout overflow issues with long content in v1 and v2 layouts) reported by @gian21391
- [PR 11589](https://github.com/firefly-iii/firefly-iii/pull/11589) (apply user-selected light/dark mode to form elements (checkboxes, date picker) [Issue 8613](https://github.com/firefly-iii/firefly-iii/issues/8613) (Some minor color issues) reported by @rumpff [Issue 7620](https://github.com/firefly-iii/firefly-iii/issues/7620) (Issues with light mode) reported by @rchl) reported by @mateuszkulapl
- [Issue 11597](https://github.com/firefly-iii/firefly-iii/issues/11597) (Changing category doesn't recompute stats) reported by @jlauwers
- [Issue 11601](https://github.com/firefly-iii/firefly-iii/issues/11601) (Only ungrouped piggy banks are listed when creating a transaction) reported by @jgmm81
- [Issue 11614](https://github.com/firefly-iii/firefly-iii/issues/11614) (Add New Taiwan Dollar to Currency Seeder) reported by @nick322
- [Issue 11620](https://github.com/firefly-iii/firefly-iii/issues/11620) (Add database indexes to improve reporting query performance) reported by @Zakmaf
- [PR 11632](https://github.com/firefly-iii/firefly-iii/pull/11632) (fix v2 layout dashboard transactions load) reported by @mateuszkulapl
- [Issue 11657](https://github.com/firefly-iii/firefly-iii/issues/11657) (since v6.4.16: Backslash in category names cannot be matched in rules) reported by @37-b-j
- [Issue 11660](https://github.com/firefly-iii/firefly-iii/issues/11660) (Display of negative values of transfers) reported by @Robubble
- Confirming your new email address would result in an error.
### Security
- Initial release.
### API
- [API end point](https://api-docs.firefly-iii.org/) `/v1/api/batch/finish`.

52
composer.lock generated
View File

@@ -130,16 +130,16 @@
},
{
"name": "brick/math",
"version": "0.14.6",
"version": "0.14.7",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "32498d5e1897e7642c0b961ace2df6d7dc9a3bc3"
"reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/32498d5e1897e7642c0b961ace2df6d7dc9a3bc3",
"reference": "32498d5e1897e7642c0b961ace2df6d7dc9a3bc3",
"url": "https://api.github.com/repos/brick/math/zipball/07ff363b16ef8aca9692bba3be9e73fe63f34e50",
"reference": "07ff363b16ef8aca9692bba3be9e73fe63f34e50",
"shasum": ""
},
"require": {
@@ -178,7 +178,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.14.6"
"source": "https://github.com/brick/math/tree/0.14.7"
},
"funding": [
{
@@ -186,7 +186,7 @@
"type": "github"
}
],
"time": "2026-02-05T07:59:58+00:00"
"time": "2026-02-07T10:57:35+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
@@ -3829,16 +3829,16 @@
},
{
"name": "nette/schema",
"version": "v1.3.3",
"version": "v1.3.4",
"source": {
"type": "git",
"url": "https://github.com/nette/schema.git",
"reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004"
"reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004",
"reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004",
"url": "https://api.github.com/repos/nette/schema/zipball/086497a2f34b82fede9b5a41cc8e131d087cd8f7",
"reference": "086497a2f34b82fede9b5a41cc8e131d087cd8f7",
"shasum": ""
},
"require": {
@@ -3846,8 +3846,8 @@
"php": "8.1 - 8.5"
},
"require-dev": {
"nette/tester": "^2.5.2",
"phpstan/phpstan-nette": "^2.0@stable",
"nette/tester": "^2.6",
"phpstan/phpstan": "^2.0@stable",
"tracy/tracy": "^2.8"
},
"type": "library",
@@ -3888,9 +3888,9 @@
],
"support": {
"issues": "https://github.com/nette/schema/issues",
"source": "https://github.com/nette/schema/tree/v1.3.3"
"source": "https://github.com/nette/schema/tree/v1.3.4"
},
"time": "2025-10-30T22:57:59+00:00"
"time": "2026-02-08T02:54:00+00:00"
},
{
"name": "nette/utils",
@@ -10530,16 +10530,16 @@
},
{
"name": "fruitcake/laravel-debugbar",
"version": "v4.0.6",
"version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/fruitcake/laravel-debugbar.git",
"reference": "0cbf2986de59f66870cee565491b81eb89f8d25e"
"reference": "a9cc62c81cd0bda4ca7410229487638d7df786be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/0cbf2986de59f66870cee565491b81eb89f8d25e",
"reference": "0cbf2986de59f66870cee565491b81eb89f8d25e",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/a9cc62c81cd0bda4ca7410229487638d7df786be",
"reference": "a9cc62c81cd0bda4ca7410229487638d7df786be",
"shasum": ""
},
"require": {
@@ -10616,7 +10616,7 @@
],
"support": {
"issues": "https://github.com/fruitcake/laravel-debugbar/issues",
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.0.6"
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v4.0.7"
},
"funding": [
{
@@ -10628,7 +10628,7 @@
"type": "github"
}
],
"time": "2026-02-04T11:48:53+00:00"
"time": "2026-02-06T20:53:50+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@@ -11198,16 +11198,16 @@
},
{
"name": "php-debugbar/php-debugbar",
"version": "v3.3.0",
"version": "v3.3.1",
"source": {
"type": "git",
"url": "https://github.com/php-debugbar/php-debugbar.git",
"reference": "e22287890107602af6a113dc7975b3d77c542e5f"
"reference": "afdaa2e56aca9d56b5bb2bad041bd2f6002017cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/e22287890107602af6a113dc7975b3d77c542e5f",
"reference": "e22287890107602af6a113dc7975b3d77c542e5f",
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/afdaa2e56aca9d56b5bb2bad041bd2f6002017cf",
"reference": "afdaa2e56aca9d56b5bb2bad041bd2f6002017cf",
"shasum": ""
},
"require": {
@@ -11284,7 +11284,7 @@
],
"support": {
"issues": "https://github.com/php-debugbar/php-debugbar/issues",
"source": "https://github.com/php-debugbar/php-debugbar/tree/v3.3.0"
"source": "https://github.com/php-debugbar/php-debugbar/tree/v3.3.1"
},
"funding": [
{
@@ -11296,7 +11296,7 @@
"type": "github"
}
],
"time": "2026-01-28T12:57:47+00:00"
"time": "2026-02-06T21:09:38+00:00"
},
{
"name": "php-debugbar/symfony-bridge",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => (bool)envNonEmpty('USE_RUNNING_BALANCE', true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-02-06',
'build_time' => 1770398635,
'version' => 'develop/2026-02-08',
'build_time' => 1770531224,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

View File

@@ -33,16 +33,12 @@ class ChangesFor3101 extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.
*
* @SuppressWarnings("PHPMD.ShortMethodName")
*/
public function up(): void
{
}
public function up(): void {}
}

View File

@@ -39,9 +39,7 @@ class FixNullables extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -34,16 +34,12 @@ class ChangesForV474 extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.
*
* @SuppressWarnings("PHPMD.ShortMethodName")
*/
public function up(): void
{
}
public function up(): void {}
}

View File

@@ -37,9 +37,7 @@ class ChangesForV4711 extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -37,9 +37,7 @@ class ChangesForV4712 extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -35,9 +35,7 @@ class ExtendCurrencyInfo extends Migration
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -34,9 +34,7 @@ return new class() extends Migration {
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -31,9 +31,7 @@ return new class() extends Migration {
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

View File

@@ -51,9 +51,7 @@ return new class() extends Migration {
/**
* Reverse the migrations.
*/
public function down(): void
{
}
public function down(): void {}
/**
* Run the migrations.

6
package-lock.json generated
View File

@@ -3246,9 +3246,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "25.2.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz",
"integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==",
"version": "25.2.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.2.tgz",
"integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -149,13 +149,13 @@ OK, that was a joke. But for real, when you feel Firefly III made your life bett
### Sponsorships
Firefly III is sponsored by LamdaTest. Their support allows me to test Firefly III more easily and introduce even fewer bugs with every release.
Firefly III is sponsored by TestMu AI. Their support allows me to test Firefly III more easily and introduce even fewer bugs with every release.
<p style="font-size:21px; color:black;">Browser testing via
<a href="https://www.lambdatest.com/?utm_source=fireflyiii&utm_medium=sponsor" target="_blank">
<img src="https://www.lambdatest.com/blue-logo.png" style="vertical-align: middle;" width="250" height="45" />
Browser testing via:
<a href="https://www.testmuai.com/?utm_source=fireflyiii&utm_medium=sponsor" target="_blank">
<img src=".github/assets/img/testmu.png" alt="Testmu" style="vertical-align: middle;" width="250" />
</a>
</p>
<!-- END OF SPONSOR TEXT -->

View File

@@ -64,7 +64,7 @@
{% if group.transaction_type == 'Deposit' %}
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places) }}
{% if convertToPrimary and 0 != sum.pc_amount %}
({{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% if loop.index != group.sums|length %},{% endif %}
@@ -72,14 +72,14 @@
<span class="text-info money-transfer">
{{ formatAmountBySymbol(sum.amount*-1, sum.currency_symbol, sum.currency_decimal_places, false) }}
{% if convertToPrimary and 0 != sum.pc_amount %}
({{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% if loop.index != group.sums|length %},{% endif %}
</span>
{% else %}
{{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_decimal_places) }}
{% if convertToPrimary and 0 != sum.pc_amount %}
({{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(sum.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% if loop.index != group.sums|length %},{% endif %}
{% endif %}
@@ -174,13 +174,18 @@
{% endif %}
{# primary currency amount of deposit #}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{# transfer #}
{% elseif transaction.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
{# amount of transfer #}
{% if transaction.source_account_id == account.id %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places, false) }}
{% endif %}
{% if transaction.source_account_id != account.id %}
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_decimal_places, false) }}
{% endif %}
{# foreign amount of transfer #}
{% if null != transaction.foreign_amount %}
@@ -189,7 +194,7 @@
{# primary currency amount of transfer #}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
</span>
{# opening balance #}
@@ -200,7 +205,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
@@ -208,7 +213,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% endif %}
{# reconciliation #}
@@ -219,7 +224,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_decimal_places) }}
@@ -227,7 +232,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% endif %}
{# liability credit #}
@@ -238,7 +243,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.amount*-1, transaction.currency_symbol, transaction.currency_decimal_places) }}
@@ -246,7 +251,7 @@
({{ formatAmountBySymbol(transaction.foreign_amount*-1, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }})
{% endif %}
{% if convertToPrimary and 0 != transaction.pc_amount %}
({{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% endif %}
@@ -262,12 +267,12 @@
{% endif %}
{# primary currency amount of withdrawal, if not in foreign currency #}
{% if convertToPrimary and 0 != transaction.pc_amount and primaryCurrency.id != transaction.foreign_currency_id %}
({{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(transaction.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% endif %}
{% endif %}
</td>
{% if (fireflyiiiconfig('use_running_balance', true)) %}
<td>
<td style="{{ style|raw }};text-align:right">
{# RUNNING BALANCE #}
{% if (null == transaction.balance_dirty or false == transaction.balance_dirty) and null != transaction.destination_balance_after and null != transaction.source_balance_after %}
{% if transaction.transaction_type_type == 'Deposit' %}

View File

@@ -164,13 +164,12 @@
<td>
{% for amount in amounts %}
{% if first.transaction_type_type == 'Withdrawal' %}
{{ formatAmountBySymbol(amount.amount,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
{% if amount.approximate %}~ {% endif %}{{ formatAmountBySymbol(amount.amount,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
{% elseif first.transaction_type_type == 'Deposit' %}
{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
{% if amount.approximate %}~ {% endif %}{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
{% elseif first.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
{{ formatAmountBySymbol(amount.amount*-1, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
{% if amount.approximate %}~ {% endif %}{{ formatAmountBySymbol(amount.amount*-1, amount.symbol, amount.decimal_places, false) }}{% if loop.index0 != amounts|length -1 %}, {% endif %}
</span>
{% elseif first.transaction_type_type == 'Opening balance' %}
{# Opening balance stored amount is always negative: find out which way the money goes #}
@@ -318,12 +317,12 @@
<!-- do primary currency amount, if foreign amount is not the same. -->
{% if null != journal.pc_amount and primaryCurrency.id != journal.currency_id and primaryCurrency.id != journal.foreign_currency_id %}
{% if first.transaction_type_type == 'Withdrawal' %}
({{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(journal.pc_amount, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% elseif first.transaction_type_type == 'Deposit' %}
({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
(~ {{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places) }})
{% elseif first.transaction_type_type == 'Transfer' %}
<span class="text-info money-transfer">
({{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }})
(~ {{ formatAmountBySymbol(journal.pc_amount*-1, primaryCurrency.symbol, primaryCurrency.decimal_places, false) }})
</span>
{% endif %}
{% endif %}