diff --git a/docs/docs/report/helpers.md b/docs/docs/report/helpers.md index 1aa82e2f29..1cde507797 100644 --- a/docs/docs/report/helpers.md +++ b/docs/docs/report/helpers.md @@ -556,9 +556,28 @@ The locale is resolved in the following priority order: 1. **Explicit `locale=` argument** — highest priority, always wins 2. **Server `LANGUAGE_CODE`** — fallback +#### Leading Digits + +The `leading` argument specifies the minimum number of digits to render before the decimal point (zero-padded). This works identically to `leading` in [`format_number`](#format_number): + +```html +{% raw %} +{% load report %} + + +{% render_currency order.total_price currency='USD' %} + + + +{% render_currency order.total_price currency='USD' leading=4 %} + + +{% endraw %} +``` + #### Custom Format Strings -The `fmt` argument accepts a [Unicode number pattern](https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns) string (same syntax as [`format_number`](#custom-format-strings)). **When `fmt` is provided, it takes complete priority over `decimal_places` and `max_decimal_places`** — those arguments are ignored. +The `fmt` argument accepts a [Unicode number pattern](https://unicode.org/reports/tr35/tr35-numbers.html#Number_Format_Patterns) string (same syntax as [`format_number`](#custom-format-strings)). **When `fmt` is provided, it takes complete priority over `decimal_places`, `max_decimal_places`, and `leading`** — those arguments are ignored. The `locale`, `currency`, `multiplier`, and `include_symbol` arguments are still applied when `fmt` is set. diff --git a/src/backend/InvenTree/report/templatetags/report.py b/src/backend/InvenTree/report/templatetags/report.py index 8561deb2e5..73483a0723 100644 --- a/src/backend/InvenTree/report/templatetags/report.py +++ b/src/backend/InvenTree/report/templatetags/report.py @@ -797,12 +797,13 @@ def modulo(x: Any, y: Any, cast: Optional[type] = None) -> Any: @register.simple_tag def render_currency( - money, + money: Money | str | int | float | Decimal, decimal_places: Optional[int] = None, currency: Optional[str] = None, multiplier: Optional[Decimal] = None, max_decimal_places: Optional[int] = None, include_symbol: bool = True, + leading: Optional[int] = None, fmt: Optional[str] = None, locale: Optional[str] = None, **kwargs, @@ -816,12 +817,14 @@ def render_currency( decimal_places: Minimum (forced) decimal places, e.g. decimal_places=2 gives '.00'. Defaults to the locale/currency standard. max_decimal_places: Maximum decimal places (optional digits beyond decimal_places), e.g. max_decimal_places=4 allows up to 4. include_symbol: If True, include the currency symbol in the output - fmt: Optional Babel number pattern string. When provided, takes priority over all decimal_places options. + leading: Minimum number of leading digits to render before the decimal point (default = 1) + fmt: Optional Babel number pattern string. When provided, takes priority over all other formatting options. locale: Optional locale override (e.g. 'en-us', 'de-de'). Defaults to server LANGUAGE_CODE. """ if money in [None, '']: return '-' + # If the supplied value is *not* a Money instance, attempt to convert it into one if not isinstance(money, Money): try: money = Money( @@ -829,9 +832,7 @@ def render_currency( currency or get_global_setting('INVENTREE_DEFAULT_CURRENCY'), ) except Exception: - raise ValidationError( - 'render_currency: invalid money value: ' + repr(money) - ) + raise ValidationError(f'render_currency: invalid money value - {money!r}') if currency is not None: try: @@ -844,7 +845,7 @@ def render_currency( money *= Decimal(str(multiplier).strip()) except Exception: raise ValidationError( - 'render_currency: invalid multiplier value: ' + repr(multiplier) + f'render_currency: invalid multiplier value - {multiplier!r}' ) locale = get_locale(locale) @@ -870,6 +871,15 @@ def render_currency( pattern.frac_prec = (decimal_places, max(decimal_places, max_decimal_places)) + if leading is not None: + try: + leading = int(leading) or 0 + except (ValueError, TypeError): + leading = 0 + if leading > 0: + min_int, max_int = pattern.int_prec + pattern.int_prec = (max(leading, min_int), max(leading, max_int)) + return pattern.apply( money.amount, locale, diff --git a/src/backend/InvenTree/report/test_tags.py b/src/backend/InvenTree/report/test_tags.py index 04c3ca9295..92b970d0b2 100644 --- a/src/backend/InvenTree/report/test_tags.py +++ b/src/backend/InvenTree/report/test_tags.py @@ -609,6 +609,24 @@ class ReportTagTest(PartImageTestMixin, InvenTreeTestCase): report_tags.render_currency(m, fmt='0.0000', decimal_places=2), '1234.5600' ) + # Test leading digits + m_small = Money(1.23, 'USD') + self.assertEqual( + report_tags.render_currency(m_small, leading=4, locale='en-us'), '$0,001.23' + ) + # leading=1 is the default — no change + self.assertEqual( + report_tags.render_currency(m_small, leading=1, locale='en-us'), '$1.23' + ) + # invalid leading falls back gracefully + self.assertEqual( + report_tags.render_currency(m_small, leading='x', locale='en-us'), '$1.23' + ) + # fmt takes priority over leading + self.assertEqual( + report_tags.render_currency(m_small, leading=6, fmt='#,##0.00'), '1.23' + ) + def test_render_currency_locale_override(self): """Explicit locale= kwarg takes priority over global setting and system locale.""" m = Money(1234.56, 'USD')