Fix for issue #1253 (Budget can't be balanced when "Hide decimal places" in the setting is on) (#6274)

* Fix number formatting of intlFormatter with a wrapper to handle -0 edge case (#1253)

* Fix Normalize integer currency values in makeBalanceAmountStyle function based on formatting user prefs (#1253)

* [autofix.ci] apply automated fixes

* Add release notes for budget balancing issue when "Hide decimal places" is enabled

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Matiss Janis Aboltins <matiss@mja.lv>
This commit is contained in:
Roberto Carlos Gomez Araque
2025-12-21 12:39:01 -05:00
committed by GitHub
parent cee994cf97
commit 5629e238d6
5 changed files with 43 additions and 5 deletions

View File

@@ -7,6 +7,7 @@ import { t } from 'i18next';
import { send } from 'loot-core/platform/client/fetch';
import * as monthUtils from 'loot-core/shared/months';
import { currencyToAmount, integerToCurrency } from 'loot-core/shared/util';
import { type Handlers } from 'loot-core/types/handlers';
import {
type CategoryEntity,
@@ -71,17 +72,29 @@ export function makeBalanceAmountStyle(
goalValue?: number | null,
budgetedValue?: number | null,
) {
if (value < 0) {
// Converts an integer currency value to a normalized decimal amount.
// First converts the integer to currency format, then to a decimal amount.
// Uses integerToCurrency to display the value correctly according to user prefs.
const normalizeIntegerValue = (val: number | null | undefined) =>
typeof val === 'number' ? currencyToAmount(integerToCurrency(val)) : 0;
const currencyValue = normalizeIntegerValue(value);
if (currencyValue < 0) {
return { color: theme.errorText };
}
if (goalValue == null) {
const greyed = makeAmountGrey(value);
const greyed = makeAmountGrey(currencyValue);
if (greyed) {
return greyed;
}
} else {
if (budgetedValue < goalValue) {
const budgetedAmount = normalizeIntegerValue(budgetedValue);
const goalAmount = normalizeIntegerValue(goalValue);
if (budgetedAmount < goalAmount) {
return { color: theme.warningText };
}
return { color: theme.noticeText };

View File

@@ -41,7 +41,7 @@ export type FormatResult = {
function format(
value: unknown,
type: FormatType,
formatter: Intl.NumberFormat,
formatter: { format: (value: number) => string },
decimalPlaces: number,
): FormatResult {
switch (type) {

View File

@@ -123,6 +123,15 @@ describe('utility functions', () => {
expect(formatter.format(Number('1234.56'))).toBe(`1\u2019235`);
});
test('number formatting works with small negative numbers with 0 decimal places', () => {
setNumberFormat({ format: 'comma-dot', hideFraction: true });
const formatter = getNumberFormat().formatter;
expect(formatter.format(Number('-0.1'))).toBe('0');
expect(formatter.format(Number('-0.5'))).toBe('-1');
expect(formatter.format(Number('-0.9'))).toBe('-1');
expect(formatter.format(Number('-1.2'))).toBe('-1');
});
test('currencyToAmount works with basic numbers', () => {
expect(currencyToAmount('3')).toBe(3);
expect(currencyToAmount('3.4')).toBe(3.4);

View File

@@ -370,11 +370,21 @@ export function getNumberFormat({
maximumFractionDigits: currentHideFraction ? 0 : 2,
};
const intlFormatter = new Intl.NumberFormat(locale, fractionDigitsOptions);
// Wrapper to handle -0 edge case
const formatter = {
format: (value: number) => {
const formatted = intlFormatter.format(value);
return formatted === '-0' ? '0' : formatted;
},
};
return {
value: currentFormat,
thousandsSeparator,
decimalSeparator,
formatter: new Intl.NumberFormat(locale, fractionDigitsOptions),
formatter,
};
}

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [rcgomezap]
---
Fix for budget can't be balanced when "Hide decimal places" in the setting is on