Changeset - d66ba8773f5e
[Not reviewed]
0 4 0
Brett Smith - 4 years ago 2020-04-09 18:13:07
brettcsmith@brettcsmith.org
data: Make balance_of currency-aware.
4 files changed with 48 insertions and 19 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/data.py
Show inline comments
...
 
@@ -27,2 +27,3 @@ from beancount.core import account as bc_account
 
from beancount.core import amount as bc_amount
 
from beancount.core import convert as bc_convert
 

	
...
 
@@ -259,3 +260,3 @@ def balance_of(txn: Transaction,
 
               *preds: Callable[[Account], Optional[bool]],
 
) -> decimal.Decimal:
 
) -> Amount:
 
    """Return the balance of specified postings in a transaction.
...
 
@@ -265,8 +266,18 @@ def balance_of(txn: Transaction,
 
    match any of the predicates.
 

	
 
    balance_of uses the "weight" of each posting, so the return value will
 
    use the currency of the postings' cost when available.
 
    """
 
    return sum(
 
        (post.units.number for post in iter_postings(txn)
 
         if any(pred(post.account) for pred in preds)),
 
        decimal.Decimal(0),
 
    )
 
    match_posts = [post for post in iter_postings(txn)
 
                   if any(pred(post.account) for pred in preds)]
 
    number = decimal.Decimal(0)
 
    if not match_posts:
 
        currency = ''
 
    else:
 
        weights: Sequence[Amount] = [
 
            bc_convert.get_weight(post) for post in match_posts  # type:ignore[no-untyped-call]
 
        ]
 
        number = sum((wt.number for wt in weights), number)
 
        currency = weights[0].currency
 
    return Amount._make((number, currency))
 

	
conservancy_beancount/plugin/meta_approval.py
Show inline comments
...
 
@@ -42,3 +42,3 @@ class MetaApproval(core._RequireLinksPostingMetadataHook):
 
                data.Account.is_credit_card,
 
            )
 
            ).number
 
        )
tests/test_data_balance_of.py
Show inline comments
...
 
@@ -26,2 +26,3 @@ from conservancy_beancount import data
 
is_cash_eq = data.Account.is_cash_equivalent
 
USD = testutil.Amount
 

	
...
 
@@ -36,2 +37,10 @@ def payable_payment_txn():
 

	
 
@pytest.fixture
 
def fx_donation_txn():
 
    return testutil.Transaction(postings=[
 
        ('Income:Donations', -500, 'EUR', ('.9', 'USD')),
 
        ('Assets:Checking', 445),
 
        ('Expenses:BankingFees', 5),
 
    ])
 

	
 
def balance_under(txn, *accts):
...
 
@@ -45,13 +54,14 @@ def test_balance_of_simple_txn():
 
    ])
 
    assert balance_under(txn, 'Assets') == 50
 
    assert balance_under(txn, 'Income') == -50
 
    assert balance_under(txn, 'Assets') == USD(50)
 
    assert balance_under(txn, 'Income') == USD(-50)
 

	
 
def test_zero_balance_of(payable_payment_txn):
 
    assert balance_under(payable_payment_txn, 'Equity') == 0
 
    assert balance_under(payable_payment_txn, 'Assets:Cash') == 0
 
    assert balance_under(payable_payment_txn, 'Liabilities:CreditCard') == 0
 
    expected = testutil.Amount(0, '')
 
    assert balance_under(payable_payment_txn, 'Equity') == expected
 
    assert balance_under(payable_payment_txn, 'Assets:Cash') == expected
 
    assert balance_under(payable_payment_txn, 'Liabilities:CreditCard') == expected
 

	
 
def test_nonzero_balance_of(payable_payment_txn):
 
    assert balance_under(payable_payment_txn, 'Assets', 'Expenses') == -50
 
    assert balance_under(payable_payment_txn, 'Assets', 'Liabilities') == -5
 
    assert balance_under(payable_payment_txn, 'Assets', 'Expenses') == USD(-50)
 
    assert balance_under(payable_payment_txn, 'Assets', 'Liabilities') == USD(-5)
 

	
...
 
@@ -63,5 +73,10 @@ def test_multiarg_balance_of():
 
    ])
 
    assert data.balance_of(txn, is_cash_eq, data.Account.is_credit_card) == -5
 
    assert data.balance_of(txn, is_cash_eq, data.Account.is_credit_card) == USD(-5)
 

	
 
def test_balance_of_multipost_txn(payable_payment_txn):
 
    assert data.balance_of(payable_payment_txn, is_cash_eq) == -55
 
    assert data.balance_of(payable_payment_txn, is_cash_eq) == USD(-55)
 

	
 
def test_balance_of_multicurrency_txn(fx_donation_txn):
 
    assert balance_under(fx_donation_txn, 'Income') == USD(-450)
 
    assert balance_under(fx_donation_txn, 'Income', 'Assets') == USD(-5)
 
    assert balance_under(fx_donation_txn, 'Income', 'Expenses') == USD(-445)
tests/testutil.py
Show inline comments
...
 
@@ -71,2 +71,5 @@ def Amount(number, currency='USD'):
 

	
 
def Cost(number, currency='USD', date=FY_MID_DATE, label=None):
 
    return bc_data.Cost(Decimal(number), currency, date, label)
 

	
 
def Posting(account, number,
...
 
@@ -74,4 +77,4 @@ def Posting(account, number,
 
            **meta):
 
    if not (number is None or isinstance(number, Decimal)):
 
        number = Decimal(number)
 
    if cost is not None:
 
        cost = Cost(*cost)
 
    if meta is None:
...
 
@@ -80,3 +83,3 @@ def Posting(account, number,
 
        account,
 
        bc_amount.Amount(number, currency),
 
        Amount(number, currency),
 
        cost,
0 comments (0 inline, 0 general)