data: Add is_opening_balance_txn function.
This commit is contained in:
		
							parent
							
								
									d66ba8773f
								
							
						
					
					
						commit
						4eaba1ebf6
					
				
					 3 changed files with 88 additions and 1 deletions
				
			
		| 
						 | 
					@ -21,7 +21,7 @@ throughout Conservancy tools.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import collections
 | 
					import collections
 | 
				
			||||||
import decimal
 | 
					import decimal
 | 
				
			||||||
import operator
 | 
					import functools
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from beancount.core import account as bc_account
 | 
					from beancount.core import account as bc_account
 | 
				
			||||||
from beancount.core import amount as bc_amount
 | 
					from beancount.core import amount as bc_amount
 | 
				
			||||||
| 
						 | 
					@ -281,6 +281,16 @@ def balance_of(txn: Transaction,
 | 
				
			||||||
        currency = weights[0].currency
 | 
					        currency = weights[0].currency
 | 
				
			||||||
    return Amount._make((number, currency))
 | 
					    return Amount._make((number, currency))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@functools.lru_cache()
 | 
				
			||||||
 | 
					def is_opening_balance_txn(txn: Transaction) -> bool:
 | 
				
			||||||
 | 
					    opening_equity = balance_of(txn, Account.is_opening_equity)
 | 
				
			||||||
 | 
					    if not opening_equity.currency:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    rest = balance_of(txn, lambda acct: not acct.is_opening_equity())
 | 
				
			||||||
 | 
					    if not rest.currency:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    return abs(opening_equity.number + rest.number) < decimal.Decimal('.01')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def iter_postings(txn: Transaction) -> Iterator[Posting]:
 | 
					def iter_postings(txn: Transaction) -> Iterator[Posting]:
 | 
				
			||||||
    """Yield an enhanced Posting object for every posting in the transaction"""
 | 
					    """Yield an enhanced Posting object for every posting in the transaction"""
 | 
				
			||||||
    for index, source in enumerate(txn.postings):
 | 
					    for index, source in enumerate(txn.postings):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										59
									
								
								tests/test_data_is_opening_balance_txn.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								tests/test_data_is_opening_balance_txn.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					"""Test data.is_opening_balance_txn function"""
 | 
				
			||||||
 | 
					# Copyright © 2020  Brett Smith
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is free software: you can redistribute it and/or modify
 | 
				
			||||||
 | 
					# it under the terms of the GNU Affero General Public License as published by
 | 
				
			||||||
 | 
					# the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					# (at your option) any later version.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This program is distributed in the hope that it will be useful,
 | 
				
			||||||
 | 
					# but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
				
			||||||
 | 
					# GNU Affero General Public License for more details.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# You should have received a copy of the GNU Affero General Public License
 | 
				
			||||||
 | 
					# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from decimal import Decimal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import testutil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from conservancy_beancount import data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_typical_opening():
 | 
				
			||||||
 | 
					    txn = testutil.Transaction.opening_balance()
 | 
				
			||||||
 | 
					    assert data.is_opening_balance_txn(txn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_multiacct_opening():
 | 
				
			||||||
 | 
					    txn = testutil.Transaction(postings=[
 | 
				
			||||||
 | 
					        ('Assets:Receivable:Accounts', 100),
 | 
				
			||||||
 | 
					        (next(testutil.OPENING_EQUITY_ACCOUNTS), -100),
 | 
				
			||||||
 | 
					        ('Liabilities:Payable:Accounts', -150),
 | 
				
			||||||
 | 
					        (next(testutil.OPENING_EQUITY_ACCOUNTS), 150),
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					    assert data.is_opening_balance_txn(txn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_opening_with_fx():
 | 
				
			||||||
 | 
					    txn = testutil.Transaction.opening_balance()
 | 
				
			||||||
 | 
					    equity_post = txn.postings[-1]
 | 
				
			||||||
 | 
					    txn.postings[-1] = equity_post._replace(
 | 
				
			||||||
 | 
					        units=testutil.Amount(equity_post.units.number * Decimal('.9'), 'EUR'),
 | 
				
			||||||
 | 
					        cost=testutil.Cost('1.11111'),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert data.is_opening_balance_txn(txn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@pytest.mark.parametrize('acct1,acct2,number', [
 | 
				
			||||||
 | 
					    ('Assets:Receivable:Accounts', 'Income:Donations', 100),
 | 
				
			||||||
 | 
					    ('Expenses:Other', 'Liabilities:Payable:Accounts', 200),
 | 
				
			||||||
 | 
					    ('Expenses:Other', 'Equity:Retained:Costs', 300),
 | 
				
			||||||
 | 
					    # Release from restriction
 | 
				
			||||||
 | 
					    ('Equity:Funds:Unrestricted', 'Equity:Funds:Restricted', 400),
 | 
				
			||||||
 | 
					])
 | 
				
			||||||
 | 
					def test_not_opening_balance(acct1, acct2, number):
 | 
				
			||||||
 | 
					    txn = testutil.Transaction(postings=[
 | 
				
			||||||
 | 
					        (acct1, number),
 | 
				
			||||||
 | 
					        (acct2, -number),
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					    assert not data.is_opening_balance_txn(txn)
 | 
				
			||||||
| 
						 | 
					@ -107,6 +107,12 @@ NON_STRING_METADATA_VALUES = [
 | 
				
			||||||
    Amount(500, None),
 | 
					    Amount(500, None),
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					OPENING_EQUITY_ACCOUNTS = itertools.cycle([
 | 
				
			||||||
 | 
					    'Equity:Funds:Unrestricted',
 | 
				
			||||||
 | 
					    'Equity:Funds:Restricted',
 | 
				
			||||||
 | 
					    'Equity:OpeningBalance',
 | 
				
			||||||
 | 
					])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Transaction:
 | 
					class Transaction:
 | 
				
			||||||
    def __init__(self,
 | 
					    def __init__(self,
 | 
				
			||||||
                 date=FY_MID_DATE, flag='*', payee=None,
 | 
					                 date=FY_MID_DATE, flag='*', payee=None,
 | 
				
			||||||
| 
						 | 
					@ -147,6 +153,18 @@ class Transaction:
 | 
				
			||||||
            posting = arg
 | 
					            posting = arg
 | 
				
			||||||
        self.postings.append(posting)
 | 
					        self.postings.append(posting)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def opening_balance(cls, acct=None, **txn_meta):
 | 
				
			||||||
 | 
					        if acct is None:
 | 
				
			||||||
 | 
					            acct = next(OPENING_EQUITY_ACCOUNTS)
 | 
				
			||||||
 | 
					        return cls(**txn_meta, postings=[
 | 
				
			||||||
 | 
					            ('Assets:Receivable:Accounts', 100),
 | 
				
			||||||
 | 
					            ('Assets:Receivable:Loans', 200),
 | 
				
			||||||
 | 
					            ('Liabilities:Payable:Accounts', -15),
 | 
				
			||||||
 | 
					            ('Liabilities:Payable:Vacation', -25),
 | 
				
			||||||
 | 
					            (acct, -260),
 | 
				
			||||||
 | 
					        ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestConfig:
 | 
					class TestConfig:
 | 
				
			||||||
    def __init__(self, *,
 | 
					    def __init__(self, *,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue