reports: Improve formatting of non-currency commodities.

Introduce the get_commodity_format() function, which returns Babel's
usual format string for currencies, but returns a version of it
"merged" with the locale's currency unit pattern for other
commodities.

BaseODS then calls this function where needed to format amounts.
This commit is contained in:
Brett Smith 2020-09-04 15:29:10 -04:00
parent b4f2b506be
commit de10197af7
3 changed files with 49 additions and 14 deletions

View file

@ -737,7 +737,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
root: odf.element.Element,
locale: babel.core.Locale,
code: str,
fmt_index: int,
amount: DecimalCompat=0,
properties: Optional[odf.style.TextProperties]=None,
*,
fmt_key: Optional[str]=None,
@ -746,11 +746,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
if fmt_key is None:
fmt_key = self.currency_fmt_key
pattern = locale.currency_formats[fmt_key]
fmts = pattern.pattern.split(';')
try:
fmt = fmts[fmt_index]
except IndexError:
fmt = fmts[0]
fmt = get_commodity_format(locale, code, amount, fmt_key)
style = self.replace_child(
root,
odf.number.CurrencyStyle,
@ -823,7 +819,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
root, locale, code, 0, positive_properties, volatile=True,
)
curr_style = self._build_currency_style(
root, locale, code, 1, negative_properties,
root, locale, code, -1, negative_properties,
)
curr_style.addElement(odf.style.Map(
condition='value()>=0', applystylename=pos_style,
@ -1133,9 +1129,9 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
)
return self.currency_cell(amount, **attrs)
else:
lines = [babel.numbers.format_currency(
number, currency, locale=self.locale, format_type=self.currency_fmt_key,
) for number, currency in balance.values()]
lines = [babel.numbers.format_currency(number, currency, get_commodity_format(
self.locale, currency, None, self.currency_fmt_key,
)) for number, currency in balance.values()]
attrs['stylename'] = self.merge_styles(
attrs.get('stylename'), self.style_endtext,
)
@ -1287,6 +1283,47 @@ def account_balances(
MutableBalance(),
))
def get_commodity_format(locale: babel.core.Locale,
code: str,
amount: Optional[DecimalCompat]=None,
format_type: str='accounting',
) -> str:
"""Return a format string for a commodity
Typical use looks like::
number, code = post.units
fmt = get_commodity_format(locale, code)
units_s = babel.numbers.format_currency(number, code, fmt)
When the commodity code refers to a real currency, you get the same format
string provided by Babel.
For other commodities like stock, you get a format code built from the
locale's currency unit pattern.
If ``amount`` is defined, the format string will be specifically for that
number, whether positive or negative. Otherwise, the format string may
define both positive and negative formats.
"""
fmt: str = locale.currency_formats[format_type].pattern
if amount is not None:
fmt, _, neg_fmt = fmt.partition(';')
if amount < 0 and neg_fmt:
fmt = neg_fmt
symbol = babel.numbers.get_currency_symbol(code, locale)
if symbol != code:
return fmt
else:
long_fmt: str = babel.numbers.get_currency_unit_pattern(code, locale=locale)
return re.sub(
r'[#0,.\s¤]+',
lambda match: long_fmt.format(
match.group(0).replace('¤', '').strip(), '¤¤',
),
fmt,
)
def normalize_amount_func(account_name: str) -> Callable[[T], T]:
"""Get a function to normalize amounts for reporting

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup(
name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books",
version='1.9.5',
version='1.9.6',
author='Software Freedom Conservancy',
author_email='info@sfconservancy.org',
license='GNU AGPLv3+',

View file

@ -549,9 +549,7 @@ def test_ods_writer_balance_cell_multi_currency(ods_writer):
]]
balance = core.Balance(amounts)
cell = ods_writer.balance_cell(balance)
assert cell.text == '\0'.join(babel.numbers.format_currency(
number, currency, locale=EN_US, format_type='accounting',
) for number, currency in amounts)
assert cell.text == '2,500.00 RUB\0R$3,500.00'
@pytest.mark.parametrize('cell_source,style_name', testutil.combine_values(
CURRENCY_CELL_DATA,