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, root: odf.element.Element,
locale: babel.core.Locale, locale: babel.core.Locale,
code: str, code: str,
fmt_index: int, amount: DecimalCompat=0,
properties: Optional[odf.style.TextProperties]=None, properties: Optional[odf.style.TextProperties]=None,
*, *,
fmt_key: Optional[str]=None, fmt_key: Optional[str]=None,
@ -746,11 +746,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
if fmt_key is None: if fmt_key is None:
fmt_key = self.currency_fmt_key fmt_key = self.currency_fmt_key
pattern = locale.currency_formats[fmt_key] pattern = locale.currency_formats[fmt_key]
fmts = pattern.pattern.split(';') fmt = get_commodity_format(locale, code, amount, fmt_key)
try:
fmt = fmts[fmt_index]
except IndexError:
fmt = fmts[0]
style = self.replace_child( style = self.replace_child(
root, root,
odf.number.CurrencyStyle, odf.number.CurrencyStyle,
@ -823,7 +819,7 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
root, locale, code, 0, positive_properties, volatile=True, root, locale, code, 0, positive_properties, volatile=True,
) )
curr_style = self._build_currency_style( curr_style = self._build_currency_style(
root, locale, code, 1, negative_properties, root, locale, code, -1, negative_properties,
) )
curr_style.addElement(odf.style.Map( curr_style.addElement(odf.style.Map(
condition='value()>=0', applystylename=pos_style, condition='value()>=0', applystylename=pos_style,
@ -1133,9 +1129,9 @@ class BaseODS(BaseSpreadsheet[RT, ST], metaclass=abc.ABCMeta):
) )
return self.currency_cell(amount, **attrs) return self.currency_cell(amount, **attrs)
else: else:
lines = [babel.numbers.format_currency( lines = [babel.numbers.format_currency(number, currency, get_commodity_format(
number, currency, locale=self.locale, format_type=self.currency_fmt_key, self.locale, currency, None, self.currency_fmt_key,
) for number, currency in balance.values()] )) for number, currency in balance.values()]
attrs['stylename'] = self.merge_styles( attrs['stylename'] = self.merge_styles(
attrs.get('stylename'), self.style_endtext, attrs.get('stylename'), self.style_endtext,
) )
@ -1287,6 +1283,47 @@ def account_balances(
MutableBalance(), 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]: def normalize_amount_func(account_name: str) -> Callable[[T], T]:
"""Get a function to normalize amounts for reporting """Get a function to normalize amounts for reporting

View file

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

View file

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