Compare commits

..

12 Commits

Author SHA1 Message Date
github-actions[bot]
6c27424bfe Merge pull request #12219 from firefly-iii/release-1777868588
🤖 Automatically merge the PR into the develop branch.
2026-05-04 06:23:15 +02:00
JC5
38576f7fe0 🤖 Auto commit for release 'develop' on 2026-05-04 2026-05-04 06:23:09 +02:00
github-actions[bot]
5b80a5bdbe Merge pull request #12215 from firefly-iii/release-1777795578
🤖 Automatically merge the PR into the develop branch.
2026-05-03 10:06:25 +02:00
JC5
0202f4abd9 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 10:06:18 +02:00
James Cole
615d568479 Change sentence 2026-05-03 10:00:29 +02:00
github-actions[bot]
2ace0d3f23 Merge pull request #12214 from firefly-iii/release-1777794370
🤖 Automatically merge the PR into the develop branch.
2026-05-03 09:46:17 +02:00
JC5
42204f8dc1 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 09:46:10 +02:00
James Cole
cfac8fa569 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-05-03 09:24:38 +02:00
James Cole
04704392f3 Fix amount display in budget overview. 2026-05-03 09:24:03 +02:00
github-actions[bot]
4b9bbc9d6a Merge pull request #12213 from firefly-iii/release-1777786936
🤖 Automatically merge the PR into the develop branch.
2026-05-03 07:42:23 +02:00
JC5
f24f535d39 🤖 Auto commit for release 'develop' on 2026-05-03 2026-05-03 07:42:17 +02:00
James Cole
3a9ac03358 Add entry in preferences. 2026-05-02 15:04:23 +02:00
9 changed files with 67 additions and 55 deletions

View File

@@ -246,6 +246,7 @@ final class IndexController extends Controller
$currency = $limit->transactionCurrency ?? $primaryCurrency;
$amount = Steam::bcround($limit->amount, $currency->decimal_places);
$spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentAmount = $spent[$currency->id]['sum'] ?? '0';
$array['budgeted'][] = [
'id' => $limit->id,
@@ -283,10 +284,10 @@ final class IndexController extends Controller
if (array_key_exists($currency->id, $spentArr) && array_key_exists('sum', $spentArr[$currency->id])) {
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
$array['spent'][$currency->id]['spent_outside'] = bcmul(
bcsub($spentInLimits[$currency->id], $spentArr[$currency->id]['sum']),
'-1'
);
$array['spent'][$currency->id]['spent_outside'] = Steam::negative(bcsub(
$spentInLimits[$currency->id],
$spentArr[$currency->id]['sum']
));
$array['spent'][$currency->id]['currency_id'] = $currency->id;
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;

View File

@@ -335,7 +335,9 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d');
$limit->start_date_tz = $data['start_date']->format('e');
$limit->end_date = $data['end_date']->format('Y-m-d');
$limit->end_date_tz = $data['end_date']->format('e');
$limit->amount = $data['amount'];
$limit->generated = $data['generated'] ?? false;
$limit->period = $data['period'] ?? '';

24
composer.lock generated
View File

@@ -10176,16 +10176,16 @@
},
{
"name": "carthage-software/mago",
"version": "1.25.1",
"version": "1.25.2",
"source": {
"type": "git",
"url": "https://github.com/carthage-software/mago.git",
"reference": "b00c5dc1c73eaa9d7af7d24fcff68d3ca3c07806"
"reference": "54e9645d15381aa78bf7113e01dcd1734ec43737"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/carthage-software/mago/zipball/b00c5dc1c73eaa9d7af7d24fcff68d3ca3c07806",
"reference": "b00c5dc1c73eaa9d7af7d24fcff68d3ca3c07806",
"url": "https://api.github.com/repos/carthage-software/mago/zipball/54e9645d15381aa78bf7113e01dcd1734ec43737",
"reference": "54e9645d15381aa78bf7113e01dcd1734ec43737",
"shasum": ""
},
"require": {
@@ -10220,7 +10220,7 @@
],
"support": {
"issues": "https://github.com/carthage-software/mago/issues",
"source": "https://github.com/carthage-software/mago/tree/1.25.1"
"source": "https://github.com/carthage-software/mago/tree/1.25.2"
},
"funding": [
{
@@ -10228,7 +10228,7 @@
"type": "github"
}
],
"time": "2026-04-30T21:09:36+00:00"
"time": "2026-05-02T23:21:31+00:00"
},
{
"name": "cloudcreativity/json-api-testing",
@@ -11597,16 +11597,16 @@
},
{
"name": "phpstan/phpstan-strict-rules",
"version": "2.0.10",
"version": "2.0.11",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-strict-rules.git",
"reference": "1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f"
"reference": "9b000a578b85b32945b358b172c7b20e91189024"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f",
"reference": "1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f",
"url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/9b000a578b85b32945b358b172c7b20e91189024",
"reference": "9b000a578b85b32945b358b172c7b20e91189024",
"shasum": ""
},
"require": {
@@ -11642,9 +11642,9 @@
],
"support": {
"issues": "https://github.com/phpstan/phpstan-strict-rules/issues",
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.10"
"source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.11"
},
"time": "2026-02-11T14:17:32+00:00"
"time": "2026-05-02T06:54:10+00:00"
},
{
"name": "phpunit/php-code-coverage",

View File

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

32
package-lock.json generated
View File

@@ -2523,9 +2523,9 @@
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -3587,13 +3587,13 @@
}
},
"node_modules/axios": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz",
"integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
"integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.11",
"follow-redirects": "^1.16.0",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}
@@ -3699,9 +3699,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.25",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.25.tgz",
"integrity": "sha512-QO/VHsXCQdnzADMfmkeOPvHdIAkoB7i0/rGjINPJEetLx75hNttVWGQ/jycHUDP9zZ9rupbm60WRxcwViB0MiA==",
"version": "2.10.27",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz",
"integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -6895,9 +6895,9 @@
}
},
"node_modules/ipaddr.js": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz",
"integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==",
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz",
"integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==",
"dev": true,
"license": "MIT",
"engines": {
@@ -12171,9 +12171,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"

View File

@@ -1398,6 +1398,7 @@ return [
'pref_locale' => 'Locale settings',
'pref_languages_help' => 'Firefly III supports several languages. Which one do you prefer?',
'pref_locale_help' => 'Firefly III allows you to set other local settings, like how currencies, numbers and dates are formatted. Entries in this list may not be supported by your system. Firefly III doesn\'t have the correct date settings for every locale; contact me for improvements.',
'pref_locale_exception' => 'Firefly III can only change how you view dates. Entering a date will always be formatted according to your browser settings, not Firefly III settings. If your browser is set to "English (US)" you must always enter the date formatted as mm/dd/yyyy. No setting in Firefly III can change that.',
'pref_locale_no_demo' => 'This feature won\'t work for the demo user.',
'pref_convert_to_primary' => 'Display amounts in your primary currency',
'pref_convert_to_primary_help' => 'This option will make Firefly III try to display and show your primary currency in as many places as possible, converting amounts where necessary. This sacrifices accuracy for ease of use, because conversion is not always exact. Please verify that Firefly III has the necessary conversion rates on the "exchange rates"-page.',
@@ -1813,6 +1814,13 @@ return [
'options' => 'Options',
// budgets:
'spent_this_period' => 'Spent on this budget',
'spent_this_period_per_day' => 'Spent on this budget per day (:days day(s))',
'spent_in_budget_limit_outside_period' => 'Spent on this budget, but NOT in this period',
'spent_in_budget_limit_outside_period_per_day' => 'Spent on this budget, but NOT in this period per day (:days day(s))',
'left_in_budget_limit_overview' => 'Left in this budget',
'left_in_budget_limit_per_day' => 'Left in this budget per day (:days day(s))',
'nothing_left_in_budget' => 'The budget is now empty',
'daily_budgets' => 'Daily budgets',
'weekly_budgets' => 'Weekly budgets',
'monthly_budgets' => 'Monthly budgets',

View File

@@ -2,21 +2,21 @@
{% for budgetLimit in budget.budgeted %}
<span class="left_span" data-currency="{{ budgetLimit.currency_id }}" data-limit="{{ budgetLimit.id }}" data-value="{{ budgetLimit.left }}" class="amount_left">
{# the amount left #}
{{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
<span title="{{ 'left_in_budget_limit_overview'|_ }}">{{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}</span>
{# if the budget limit is in the past, this is not interesting. #}
{# if there is nothing left, this is not interesting. #}
{% if not budgetLimit.in_past and -1 == bccomp('0',budgetLimit.left) %}
{% if 0 == budgetLimit.active_days_left %}
({{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
<span title="{{ trans('firefly.left_in_budget_limit_per_day', {days: 0}) }}">({{ formatAmountBySymbol(budgetLimit.left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(budgetLimit.left / budgetLimit.active_days_left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
<span title="{{ trans('firefly.left_in_budget_limit_per_day', {days: budgetLimit.active_days_left}) }}">({{ formatAmountBySymbol(budgetLimit.left / budgetLimit.active_days_left, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})</span>
{% endif %}
{% endif %}
{# if there is nothing left, just format 0.00 #}
{% if not budgetLimit.in_past and -1 != bccomp('0',budgetLimit.left) %}
({{ formatAmountBySymbol('0', budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
<span title="{{ 'nothing_left_in_budget'|_ }}">({{ formatAmountBySymbol('0', budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})</span>
{% endif %}
</span><br />
{% endfor %}

View File

@@ -1,10 +1,10 @@
{# this is spent in budget limits: #}
{% for budgetLimit in budget.budgeted %}
{{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}
<span title="{{ 'spent_this_period'|_ }}">{{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }}</span>
{% if 0 == budgetLimit.active_days_passed %}
({{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
<span title="{{ trans('firefly.spent_this_period_per_day', {days: 0}) }}">({{ formatAmountBySymbol(budgetLimit.spent, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})</span>
{% else %}
({{ formatAmountBySymbol(budgetLimit.spent / budgetLimit.active_days_passed, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})
<span title="{{ trans('firefly.spent_this_period_per_day', {days: budgetLimit.active_days_passed}) }}">({{ formatAmountBySymbol(budgetLimit.spent / budgetLimit.active_days_passed, budgetLimit.currency_symbol, budgetLimit.currency_decimal_places) }})</span>
{% endif %}
<br />
{% endfor %}
@@ -12,11 +12,11 @@
{# this is spent NOT in budget limits: #}
{% for spent in budget.spent %}
{% if 0 != bccomp('0', spent.spent_outside) %}
{{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }}
<span title="{{ 'spent_in_budget_limit_outside_period'|_ }}">{{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }}</span>
{% if 0 == activeDaysPassed %}
({{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }})
<span title="{{ trans('firefly.spent_in_budget_limit_outside_period_per_day', {days: 0}) }}">({{ formatAmountBySymbol(spent.spent_outside, spent.currency_symbol, spent.currency_decimal_places) }})</span>
{% else %}
({{ formatAmountBySymbol(spent.spent_outside / activeDaysPassed, spent.currency_symbol, spent.currency_decimal_places) }})
<span title="{{ trans('firefly.spent_in_budget_limit_outside_period_per_day', {days: activeDaysPassed}) }}">({{ formatAmountBySymbol(spent.spent_outside / activeDaysPassed, spent.currency_symbol, spent.currency_decimal_places) }})</span>
{% endif %}
<br />
{% endif %}

View File

@@ -44,7 +44,7 @@
{# language #}
<div class="preferences-box">
<h3>{{ 'pref_languages'|_ }}</h3>
<p class="text-info">{{ 'pref_languages_help'|_ }}</p>
<p>{{ 'pref_languages_help'|_ }}</p>
<div class="form-group">
<div class="col-sm-12">
<select class="form-control" id="lang_holder" name="language">
@@ -63,7 +63,7 @@
</div>
<p class="text-info">
<p>
<br/>
{{ 'pref_languages_locale'|_ }}
</p>
@@ -73,7 +73,8 @@
{% if not isDocker %}
<div class="preferences-box">
<h3>{{ 'pref_locale'|_ }}</h3>
<p class="text-info">{{ 'pref_locale_help'|_ }}</p>
<p>{{ 'pref_locale_help'|_ }}</p>
<p class="text-warning">{{ 'pref_locale_exception'|_ }}</p>
<div class="form-group">
<div class="col-sm-12">
<select class="form-control" id="locale_holder" name="locale">
@@ -104,7 +105,7 @@
{# fiscal year #}
<div class="preferences-box">
<h3>{{ 'pref_custom_fiscal_year'|_ }}</h3>
<p class="text-info">
<p>
{{ 'pref_custom_fiscal_year_help'|_ }}
</p>
{% set isCustomFiscalYear = customFiscalYear == 1 ? true : false %}
@@ -116,7 +117,7 @@
{% if fireflyiiiconfig('enable_exchange_rates', true) %}
<div class="preferences-box">
<h3>{{ 'pref_convert_to_primary'|_ }}</h3>
<p class="text-info">
<p>
{{ 'pref_convert_to_primary_help'|_ }}
</p>
{{ ExpandedForm.checkbox('convertToPrimary','1',convertToPrimary,{ 'label' : 'pref_convert_primary_help'|_ }) }}
@@ -125,7 +126,7 @@
{# conversion back to primary currency #}
<div class="preferences-box">
<h3>{{ 'pref_anonymous'|_ }}</h3>
<p class="text-info">
<p>
{{ 'pref_anonymous_help'|_ }}
</p>
{{ ExpandedForm.checkbox('anonymous','1',anonymous,{ 'label' : 'pref_anonymous_label'|_ }) }}
@@ -138,7 +139,7 @@
{# transaction preferences #}
<div class="preferences-box">
<h3>{{ 'pref_optional_fields_transaction'|_ }}</h3>
<p class="text-info">
<p>
{{ 'pref_optional_fields_transaction_help'|_ }}
</p>
<h4>{{ 'optional_tj_date_fields'|_ }}</h4>
@@ -169,7 +170,7 @@
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="preferences-box">
<h3>{{ 'pref_home_screen_accounts'|_ }}</h3>
<p class="text-info">{{ 'pref_home_screen_accounts_help'|_ }}</p>
<p>{{ 'pref_home_screen_accounts_help'|_ }}</p>
{% for type, accounts in groupedAccounts %}
<strong>{{ type }}</strong>
{% for id, name in accounts %}
@@ -206,7 +207,7 @@
{# view range #}
<div class="preferences-box">
<h3>{{ 'pref_view_range'|_ }}</h3>
<p class="text-info">{{ 'pref_view_range_help'|_ }}</p>
<p>{{ 'pref_view_range_help'|_ }}</p>
<div class="radio">
<label>
@@ -311,12 +312,12 @@
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="preferences-box">
<h3>{{ 'list_page_size_title'|_ }}</h3>
<p class="text-info">{{ 'list_page_size_help'|_ }}</p>
<p>{{ 'list_page_size_help'|_ }}</p>
{{ ExpandedForm.integer('listPageSize',listPageSize,{'label' : 'list_page_size_label'|_}) }}
</div>
<div class="preferences-box">
<h3>{{ 'dark_mode_preference'|_ }}</h3>
<p class="text-info">{{ 'dark_mode_preference_help'|_ }}</p>
<p>{{ 'dark_mode_preference_help'|_ }}</p>
{% for mode in availableDarkModes %}
<div class="radio">
<label>