Changeset - d7e2ab34b925
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-17 08:29:17
brettcsmith@brettcsmith.org
meta_project: Force the default project on Equity accounts.

See rationale in comments.
3 files changed with 56 insertions and 2 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/plugin/meta_project.py
Show inline comments
...
 
@@ -80,7 +80,7 @@ class MetaProject(core._NormalizePostingMetadataHook):
 
        )
 

	
 
    def _run_on_opening_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return post.account.is_under(self.RESTRICTED_FUNDS_ACCT) is not None
 
        return post.account.is_under('Equity') is not None
 

	
 
    def _run_on_other_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        if post.account.is_under('Liabilities'):
...
 
@@ -88,6 +88,7 @@ class MetaProject(core._NormalizePostingMetadataHook):
 
        else:
 
            return post.account.is_under(
 
                'Assets:Receivable',
 
                'Equity',
 
                'Expenses',
 
                'Income',
 
                self.RESTRICTED_FUNDS_ACCT,
...
 
@@ -105,6 +106,25 @@ class MetaProject(core._NormalizePostingMetadataHook):
 
    def _run_on_txn(self, txn: Transaction) -> bool:
 
        return txn.date in self.TXN_DATE_RANGE
 

	
 
    def post_run(self, txn: Transaction, post: data.Posting) -> errormod.Iter:
 
        if (post.account.is_under('Equity')
 
            and not post.account.is_under(self.RESTRICTED_FUNDS_ACCT)):
 
            # Force all unrestricted Equity accounts to have the default
 
            # project. This is what our fiscal controls policy says, and
 
            # setting it here simplifies higher-level queries and reporting.
 
            post_value = post.meta.get(self.METADATA_KEY)
 
            txn_value = txn.meta.get(self.METADATA_KEY)
 
            # Only report an error if the posting specifically had a different
 
            # value, not if it just inherited it from the transaction.
 
            if (post_value is not txn_value
 
                and post_value != self.DEFAULT_PROJECT):
 
                yield errormod.InvalidMetadataError(
 
                    txn, self.METADATA_KEY, post_value, post,
 
                )
 
            post.meta[self.METADATA_KEY] = self.DEFAULT_PROJECT
 
        else:
 
            yield from super().post_run(txn, post)
 

	
 
    def run(self, txn: Transaction) -> errormod.Iter:
 
        # mypy says we can't assign over a method.
 
        # I understand why it wants to enforce thas as a blanket rule, but
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.2.4',
 
    version='1.2.5',
 
    author='Software Freedom Conservancy',
 
    author_email='info@sfconservancy.org',
 
    license='GNU AGPLv3+',
tests/test_meta_project.py
Show inline comments
...
 
@@ -109,6 +109,8 @@ def test_which_accounts_required_on(hook, account, required):
 
    assert required == any(errors)
 

	
 
@pytest.mark.parametrize('account', [
 
    'Equity:Funds:Unrestricted',
 
    'Equity:Realized:CurrencyConversion',
 
    'Expenses:Payroll:Salary',
 
    'Expenses:Payroll:Tax',
 
    'Liabilities:Payable:Vacation',
...
 
@@ -122,6 +124,38 @@ def test_default_values(hook, account):
 
    assert not errors
 
    testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE})
 

	
 
@pytest.mark.parametrize('equity,other_acct,value', testutil.combine_values(
 
    ['Equity:Funds:Unrestricted', 'Equity:Realized:CurrencyConversion'],
 
    ['Assets:Checking', 'Liabilities:CreditCard'],
 
    VALID_VALUES,
 
))
 
def test_equity_override_txn_meta(hook, equity, other_acct, value):
 
    if value == DEFAULT_VALUE:
 
        value = f'Not{value}'
 
    txn = testutil.Transaction(**{TEST_KEY: value}, postings=[
 
        (other_acct, 100),
 
        (equity, -100),
 
    ])
 
    errors = list(hook.run(txn))
 
    assert not errors
 
    testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE})
 

	
 
@pytest.mark.parametrize('equity,other_acct,value', testutil.combine_values(
 
    ['Equity:Funds:Unrestricted', 'Equity:Realized:CurrencyConversion'],
 
    ['Assets:Checking', 'Liabilities:CreditCard'],
 
    VALID_VALUES,
 
))
 
def test_equity_override_post_meta(hook, equity, other_acct, value):
 
    if value == DEFAULT_VALUE:
 
        value = f'Not{value}'
 
    txn = testutil.Transaction(postings=[
 
        (other_acct, 100),
 
        (equity, -100, {TEST_KEY: value}),
 
    ])
 
    actual = {error.message for error in hook.run(txn)}
 
    assert actual == {f"{equity} has invalid {TEST_KEY}: {value}"}
 
    testutil.check_post_meta(txn, None, {TEST_KEY: DEFAULT_VALUE})
 

	
 
@pytest.mark.parametrize('date,required', [
 
    (testutil.EXTREME_FUTURE_DATE, False),
 
    (testutil.FUTURE_DATE, True),
0 comments (0 inline, 0 general)