meta_expense_type: Refine defaults.
* Default "management" for more accounts. * There's a good handful of accounts where in past audits, the functional split has been "Conservancy expenses are management, project expenses are program." Handle those cases too.
This commit is contained in:
parent
3519933b8c
commit
95fb8ce481
3 changed files with 62 additions and 15 deletions
|
@ -21,6 +21,9 @@ from ..beancount_types import (
|
||||||
Transaction,
|
Transaction,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FUND_KEY = 'project'
|
||||||
|
UNRESTRICTED_FUND = 'Conservancy'
|
||||||
|
|
||||||
class MetaExpenseType(core._NormalizePostingMetadataHook):
|
class MetaExpenseType(core._NormalizePostingMetadataHook):
|
||||||
VALUES_ENUM = core.MetadataEnum('expense-type', {
|
VALUES_ENUM = core.MetadataEnum('expense-type', {
|
||||||
'fundraising',
|
'fundraising',
|
||||||
|
@ -32,13 +35,35 @@ class MetaExpenseType(core._NormalizePostingMetadataHook):
|
||||||
'mgmt': 'management',
|
'mgmt': 'management',
|
||||||
})
|
})
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
'Expenses:Services:Accounting': VALUES_ENUM['management'],
|
'Expenses:Accounting': ('management', 'management'),
|
||||||
'Expenses:Services:Administration': VALUES_ENUM['management'],
|
'Expenses:BadDebt': ('management', 'program'),
|
||||||
'Expenses:Services:Fundraising': VALUES_ENUM['fundraising'],
|
'Expenses:BankingFees': ('management', 'management'),
|
||||||
|
'Expenses:ComputerEquipment': ('management', 'program'),
|
||||||
|
'Expenses:Fines': ('management', 'program'),
|
||||||
|
'Expenses:FilingFees': ('management', 'program'),
|
||||||
|
'Expenses:Hosting': ('management', 'program'),
|
||||||
|
'Expenses:Insurance': ('management', 'management'),
|
||||||
|
'Expenses:Office': ('management', 'program'),
|
||||||
|
'Expenses:Other': ('management', 'program'),
|
||||||
|
'Expenses:Phones': ('management', 'program'),
|
||||||
|
'Expenses:Postage': ('management', 'program'),
|
||||||
|
'Expenses:ProfessionalMemberships': ('management', 'program'),
|
||||||
|
'Expenses:Services:Accounting': ('management', 'management'),
|
||||||
|
'Expenses:Services:Administration': ('management', 'management'),
|
||||||
|
'Expenses:Services:Fundraising': ('fundraising', 'fundraising'),
|
||||||
|
'Expenses:Travel': ('management', 'management'),
|
||||||
}
|
}
|
||||||
|
|
||||||
def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
|
def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
|
||||||
return post.account.startswith('Expenses:')
|
return post.account.is_under('Expenses') is not None
|
||||||
|
|
||||||
def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
|
def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
|
||||||
return self.DEFAULT_VALUES.get(post.account, 'program')
|
key = post.account.is_under(*self.DEFAULT_VALUES)
|
||||||
|
if key is None:
|
||||||
|
return 'program'
|
||||||
|
else:
|
||||||
|
unrestricted, restricted = self.DEFAULT_VALUES[key]
|
||||||
|
if post.meta.get(FUND_KEY) == UNRESTRICTED_FUND:
|
||||||
|
return unrestricted
|
||||||
|
else:
|
||||||
|
return restricted
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup
|
||||||
setup(
|
setup(
|
||||||
name='conservancy_beancount',
|
name='conservancy_beancount',
|
||||||
description="Plugin, library, and reports for reading Conservancy's books",
|
description="Plugin, library, and reports for reading Conservancy's books",
|
||||||
version='1.8.6',
|
version='1.8.7',
|
||||||
author='Software Freedom Conservancy',
|
author='Software Freedom Conservancy',
|
||||||
author_email='info@sfconservancy.org',
|
author_email='info@sfconservancy.org',
|
||||||
license='GNU AGPLv3+',
|
license='GNU AGPLv3+',
|
||||||
|
|
|
@ -38,6 +38,9 @@ INVALID_VALUES = {
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_KEY = 'expense-type'
|
TEST_KEY = 'expense-type'
|
||||||
|
PROJECT_KEY = 'project'
|
||||||
|
UNRESTRICTED_FUND = 'Conservancy'
|
||||||
|
RESTRICTED_FUND = 'Alpha'
|
||||||
|
|
||||||
@pytest.fixture(scope='module')
|
@pytest.fixture(scope='module')
|
||||||
def hook():
|
def hook():
|
||||||
|
@ -102,21 +105,40 @@ def test_non_expense_accounts_skipped(hook, account):
|
||||||
assert not errors
|
assert not errors
|
||||||
testutil.check_post_meta(txn, None, meta)
|
testutil.check_post_meta(txn, None, meta)
|
||||||
|
|
||||||
@pytest.mark.parametrize('account,set_value', [
|
@pytest.mark.parametrize('account,exp_unrestricted,exp_restricted', [
|
||||||
('Expenses:Services:Accounting', 'management'),
|
('Expenses:Accounting', 'management', 'management'),
|
||||||
('Expenses:Services:Administration', 'management'),
|
('Expenses:BadDebt', 'management', 'program'),
|
||||||
('Expenses:Services:Advocacy', 'program'),
|
('Expenses:BankingFees', 'management', 'management'),
|
||||||
('Expenses:Services:Development', 'program'),
|
('Expenses:ComputerEquipment', 'management', 'program'),
|
||||||
('Expenses:Services:Fundraising', 'fundraising'),
|
('Expenses:Fines', 'management', 'program'),
|
||||||
|
('Expenses:FilingFees', 'management', 'program'),
|
||||||
|
('Expenses:Hosting', 'management', 'program'),
|
||||||
|
('Expenses:Insurance', 'management', 'management'),
|
||||||
|
('Expenses:Office', 'management', 'program'),
|
||||||
|
('Expenses:Other', 'management', 'program'),
|
||||||
|
('Expenses:Phones', 'management', 'program'),
|
||||||
|
('Expenses:Postage', 'management', 'program'),
|
||||||
|
('Expenses:ProfessionalMemberships', 'management', 'program'),
|
||||||
|
('Expenses:Services:Accounting', 'management', 'management'),
|
||||||
|
('Expenses:Services:Administration', 'management', 'management'),
|
||||||
|
('Expenses:Services:Advocacy', 'program', 'program'),
|
||||||
|
('Expenses:Services:Development', 'program', 'program'),
|
||||||
|
('Expenses:Services:Fundraising', 'fundraising', 'fundraising'),
|
||||||
|
('Expenses:Travel', 'management', 'management'),
|
||||||
])
|
])
|
||||||
def test_default_values(hook, account, set_value):
|
def test_default_values(hook, account, exp_unrestricted, exp_restricted):
|
||||||
txn = testutil.Transaction(postings=[
|
txn = testutil.Transaction(postings=[
|
||||||
('Liabilites:CreditCard', -25),
|
('Liabilites:CreditCard', -25),
|
||||||
(account, 25),
|
(account, 20, {PROJECT_KEY: UNRESTRICTED_FUND}),
|
||||||
|
(account, 5, {PROJECT_KEY: RESTRICTED_FUND}),
|
||||||
])
|
])
|
||||||
errors = list(hook.run(txn))
|
errors = list(hook.run(txn))
|
||||||
assert not errors
|
assert not errors
|
||||||
testutil.check_post_meta(txn, None, {TEST_KEY: set_value})
|
testutil.check_post_meta(
|
||||||
|
txn, None,
|
||||||
|
{TEST_KEY: exp_unrestricted, PROJECT_KEY: UNRESTRICTED_FUND},
|
||||||
|
{TEST_KEY: exp_restricted, PROJECT_KEY: RESTRICTED_FUND},
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.parametrize('date,set_value', [
|
@pytest.mark.parametrize('date,set_value', [
|
||||||
(testutil.EXTREME_FUTURE_DATE, None),
|
(testutil.EXTREME_FUTURE_DATE, None),
|
||||||
|
|
Loading…
Reference in a new issue