Changeset - de10197af7f5
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-09-04 19:29:10
brettcsmith@brettcsmith.org
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.
3 files changed with 49 insertions and 14 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/core.py
Show inline comments
...
 
@@ -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
 

	
setup.py
Show inline comments
...
 
@@ -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+',
tests/test_reports_spreadsheet.py
Show inline comments
...
 
@@ -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,
0 comments (0 inline, 0 general)