Changeset - 7f3a26b5557d
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-18 18:07:44
brettcsmith@brettcsmith.org
reports: Balance.format() accepts zero argument.

This change has the same motivation as the recent change to
BaseODS.balance_cell(): try to preserve currency information when it's
available.
3 files changed with 47 insertions and 27 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -465,2 +465,3 @@ class BalanceReport(BaseReport):
 
        date_s = posts[0].meta.date.strftime('%Y-%m-%d')
 
        balance_s = posts.balance_at_cost().format(zero="Zero balance")
 
        if index:
...
 
@@ -468,3 +469,3 @@ class BalanceReport(BaseReport):
 
        yield f"{posts.invoice}:"
 
        yield f"  {posts.balance_at_cost()} outstanding since {date_s}"
 
        yield f"  {balance_s} outstanding since {date_s}"
 

	
conservancy_beancount/reports/core.py
Show inline comments
...
 
@@ -204,5 +204,6 @@ class Balance(Mapping[str, data.Amount]):
 
    def format(self,
 
               fmt: Optional[str]='#,#00.00 ¤¤',
 
               fmt: Optional[str]='#,##0.00 ¤¤',
 
               sep: str=', ',
 
               empty: str="Zero balance",
 
               zero: Optional[str]=None,
 
               tolerance: Optional[Decimal]=None,
...
 
@@ -211,4 +212,6 @@ class Balance(Mapping[str, data.Amount]):
 

	
 
        If the balance is zero (within tolerance), returns ``empty``.
 
        Otherwise, returns a string with each amount in the balance formatted
 
        If the balance is completely empty, return ``empty``.
 
        If the balance is zero (within tolerance) and ``zero`` is specified,
 
        return ``zero``.
 
        Otherwise, return a string with each amount in the balance formatted
 
        as ``fmt``, separated by ``sep``.
...
 
@@ -218,10 +221,14 @@ class Balance(Mapping[str, data.Amount]):
 
        """
 
        amounts = list(self.clean_copy(tolerance).values())
 
        if not amounts:
 
        balance = self.clean_copy(tolerance) or self.copy(tolerance)
 
        if not balance:
 
            return empty
 
        amounts.sort(key=lambda amt: abs(amt.number), reverse=True)
 
        return sep.join(
 
            babel.numbers.format_currency(amt.number, amt.currency, fmt)
 
            for amt in amounts
 
        )
 
        elif zero is not None and balance.is_zero():
 
            return zero
 
        else:
 
            amounts = list(balance.values())
 
            amounts.sort(key=lambda amt: (-abs(amt.number), amt.currency))
 
            return sep.join(
 
                babel.numbers.format_currency(amt.number, amt.currency, fmt)
 
                for amt in amounts
 
            )
 

	
tests/test_reports_balance.py
Show inline comments
...
 
@@ -30,3 +30,3 @@ DEFAULT_STRINGS = [
 
    ({}, "Zero balance"),
 
    ({'JPY': 0, 'BRL': 0}, "Zero balance"),
 
    ({'JPY': 0, 'BRL': 0}, "0.00 BRL, 0 JPY"),
 
    ({'USD': '20.00'}, "20.00 USD"),
...
 
@@ -35,3 +35,3 @@ DEFAULT_STRINGS = [
 
    ({'USD': 10, 'EUR': '.00015'}, "10.00 USD"),
 
    ({'JPY': '-.00015'}, "Zero balance"),
 
    ({'JPY': '-.00015'}, "-0 JPY"),
 
]
...
 
@@ -387,5 +387,5 @@ def test_format_defaults(mapping, expected):
 
    ('¤##0.0', '¥5000, -€1500.00'),
 
    ('#,#00.0¤¤', '5,000JPY, -1,500.00EUR'),
 
    ('#,##0.0¤¤', '5,000JPY, -1,500.00EUR'),
 
    ('¤+##0.0;¤-##0.0', '¥+5000, €-1500.00'),
 
    ('#,#00.0 ¤¤;(#,#00.0 ¤¤)', '5,000 JPY, (1,500.00 EUR)'),
 
    ('#,##0.0 ¤¤;(#,##0.0 ¤¤)', '5,000 JPY, (1,500.00 EUR)'),
 
])
...
 
@@ -422,18 +422,30 @@ def test_format_empty(empty):
 

	
 
@pytest.mark.parametrize('tolerance', TOLERANCES)
 
def test_str_tolerance(tolerance):
 
    chf = testutil.Amount('.005', 'CHF')
 
    actual = str(core.Balance([chf], tolerance))
 
    if tolerance > chf.number:
 
        assert actual == "Zero balance"
 
    else:
 
        assert actual == "00.00 CHF"
 
@pytest.mark.parametrize('currency,fmt', itertools.product(
 
    ['USD', 'JPY', 'BRL'],
 
    [None, '¤#,##0.00', '###0.00 ¤¤'],
 
))
 
def test_format_zero_balance_fmt(currency, fmt):
 
    zero_amt = testutil.Amount(0, currency)
 
    nonzero_amt = testutil.Amount(9, currency)
 
    zero_bal = core.Balance([zero_amt])
 
    nonzero_bal = core.Balance([nonzero_amt])
 
    expected = nonzero_bal.format(fmt).replace('9', '0')
 
    assert zero_bal.format(fmt) == expected
 

	
 
@pytest.mark.parametrize('currency,fmt', testutil.combine_values(
 
    ['USD', 'JPY', 'BRL'],
 
    ["N/A", "Zero", "ø"],
 
))
 
def test_format_zero_balance_zero_str(currency, fmt):
 
    zero_amt = testutil.Amount(0, currency)
 
    zero_bal = core.Balance([zero_amt])
 
    assert zero_bal.format(zero=fmt) == fmt
 

	
 
@pytest.mark.parametrize('tolerance', TOLERANCES)
 
def test_format_tolerance(tolerance):
 
def test_format_zero_balance_with_tolerance(tolerance):
 
    chf = testutil.Amount('.005', 'CHF')
 
    actual = core.Balance([chf]).format(tolerance=tolerance)
 
    actual = core.Balance([chf]).format(zero="ø", tolerance=tolerance)
 
    if tolerance > chf.number:
 
        assert actual == "Zero balance"
 
        assert actual == "ø"
 
    else:
 
        assert actual == "00.00 CHF"
 
        assert actual == "0.00 CHF"
0 comments (0 inline, 0 general)