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:
parent
b4f2b506be
commit
de10197af7
3 changed files with 49 additions and 14 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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+',
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue