Changeset - c712105bed3c
[Not reviewed]
0 18 0
Brett Smith - 4 years ago 2020-04-03 14:34:10
brettcsmith@brettcsmith.org
Revise chart of accounts used throughout.

The main impetus of this change is to rename accounts that were outside
Beancount's accepted five root accounts, to move them into that
structure. This includes:

Accrued:*Payable: → Liabilities:Payable:*
Accrued:*Receivable: → Assets:Receivable:*
UneanedIncome:* → Liabilities:UnearnedIncome:*

Note the last change did inspire in a change to our validation rules. We no
longer require income-type on unearned income, because it's no longer
considered income at all. Once it's earned and converted to an Income
account, that has an income-type of course.

This did inspire another rename that was not required, but
provided more consistency with the other account names above:

Assets:Prepaid* → Assets:Prepaid:*

Where applicable, I have generally extended tests to make sure one of each
of the five account types is tested. (This mostly meant adding an Equity
account to the tests.) I also added tests for key parts of the hierarchy,
like Assets:Receivable and Liabilities:Payable, where applicable.

As part of this change, Account.is_real_asset() got renamed to
Account.is_cash_equivalent(), to better self-document its purpose.
18 files changed with 155 insertions and 114 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/data.py
Show inline comments
...
 
@@ -58,7 +58,7 @@ LINK_METADATA = frozenset([
 
class Account(str):
 
    """Account name string
 

	
 
    This is a string that names an account, like Accrued:AccountsPayable
 
    This is a string that names an account, like Assets:Bank:Checking
 
    or Income:Donations. This class provides additional methods for common
 
    account name parsing and queries.
 
    """
...
 
@@ -66,17 +66,17 @@ class Account(str):
 

	
 
    SEP = bc_account.sep
 

	
 
    def is_checking(self) -> bool:
 
        return self.is_real_asset() and ':Check' in self
 
    def is_cash_equivalent(self) -> bool:
 
        return (
 
            self.is_under('Assets:') is not None
 
            and self.is_under('Assets:Prepaid', 'Assets:Receivable') is None
 
        )
 

	
 
    def is_income(self) -> bool:
 
        return self.is_under('Income:', 'UnearnedIncome:') is not None
 
    def is_checking(self) -> bool:
 
        return self.is_cash_equivalent() and ':Check' in self
 

	
 
    def is_real_asset(self) -> bool:
 
        return bool(
 
            self.is_under('Assets:')
 
            and not self.is_under('Assets:PrepaidExpenses', 'Assets:PrepaidVacation')
 
        )
 
    def is_credit_card(self) -> bool:
 
        return self.is_under('Liabilities:CreditCard') is not None
 

	
 
    def is_under(self, *acct_seq: str) -> Optional[str]:
 
        """Return a match if this account is "under" a part of the hierarchy
...
 
@@ -248,7 +248,7 @@ class Posting(BasePosting):
 
                  threshold: DecimalCompat=0,
 
                  default: Optional[bool]=None,
 
    ) -> Optional[bool]:
 
        return self.account.is_real_asset() and self.is_debit(threshold, default)
 
        return self.account.is_cash_equivalent() and self.is_debit(threshold, default)
 

	
 

	
 
def iter_postings(txn: Transaction) -> Iterator[Posting]:
conservancy_beancount/plugin/meta_entity.py
Show inline comments
...
 
@@ -59,7 +59,12 @@ class MetaEntity(core.TransactionHook):
 
        if txn_entity_ok is False:
 
            yield errormod.InvalidMetadataError(txn, self.METADATA_KEY, txn_entity)
 
        for post in data.iter_postings(txn):
 
            if post.account.is_under('Assets', 'Equity', 'Liabilities'):
 
            if not post.account.is_under(
 
                    'Assets:Receivable',
 
                    'Expenses',
 
                    'Income',
 
                    'Liabilities:Payable',
 
            ):
 
                continue
 
            entity = post.meta.get(self.METADATA_KEY)
 
            if entity is None:
conservancy_beancount/plugin/meta_income_type.py
Show inline comments
...
 
@@ -30,6 +30,8 @@ class MetaIncomeType(core._NormalizePostingMetadataHook):
 
        'UBTI',
 
    })
 
    DEFAULT_VALUES = {
 
        'Income:Conferences:Registrations': 'RBI',
 
        'Income:Conferences:Sponsorship': 'RBI',
 
        'Income:Donations': 'Donations',
 
        'Income:Honoraria': 'RBI',
 
        'Income:Interest': 'RBI',
...
 
@@ -38,12 +40,10 @@ class MetaIncomeType(core._NormalizePostingMetadataHook):
 
        'Income:Sales': 'RBI',
 
        'Income:SoftwareDevelopment': 'RBI',
 
        'Income:TrademarkLicensing': 'RBI',
 
        'UnearnedIncome:Conferences:Registrations': 'RBI',
 
        'UnearnedIncome:MatchPledges': 'Donations',
 
    }
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return post.account.is_income()
 
        return post.account.is_under('Income') is not None
 

	
 
    def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
 
        try:
conservancy_beancount/plugin/meta_invoice.py
Show inline comments
...
 
@@ -26,4 +26,7 @@ class MetaInvoice(core._RequireLinksPostingMetadataHook):
 
    METADATA_KEY = 'invoice'
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return post.account.is_under('Accrued') is not None
 
        return post.account.is_under(
 
            'Assets:Receivable',
 
            'Liabilities:Payable',
 
        ) is not None
conservancy_beancount/plugin/meta_project.py
Show inline comments
...
 
@@ -79,12 +79,19 @@ class MetaProject(core._NormalizePostingMetadataHook):
 
        )
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return post.account.is_under('Assets', 'Equity', 'Liabilities') is None
 
        if post.account.is_under('Liabilities'):
 
            return not post.account.is_credit_card()
 
        else:
 
            return post.account.is_under(
 
                'Assets:Receivable',
 
                'Expenses',
 
                'Income',
 
            ) is not None
 

	
 
    def _default_value(self, txn: Transaction, post: data.Posting) -> MetaValueEnum:
 
        if post.account.is_under(
 
                'Accrued:VacationPayable',
 
                'Expenses:Payroll',
 
                'Liabilities:Payable:Vacation',
 
        ):
 
            return self.DEFAULT_PROJECT
 
        else:
conservancy_beancount/plugin/meta_receipt.py
Show inline comments
...
 
@@ -29,8 +29,8 @@ class MetaReceipt(core._RequireLinksPostingMetadataHook):
 
        self.payment_threshold = abs(config.payment_threshold())
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        return bool(
 
            (post.account.is_real_asset() or post.account.is_under('Liabilities'))
 
        return (
 
            (post.account.is_cash_equivalent() or post.account.is_credit_card())
 
            and post.units.number is not None
 
            and abs(post.units.number) >= self.payment_threshold
 
        )
...
 
@@ -52,7 +52,7 @@ class MetaReceipt(core._RequireLinksPostingMetadataHook):
 

	
 
        if post.account.is_checking():
 
            fallback_key = 'check'
 
        elif post.account.is_under('Liabilities:CreditCard') and post_amount == -1:
 
        elif post.account.is_credit_card() and post_amount == -1:
 
            fallback_key = 'invoice'
 
        elif post.account.is_under('Assets:PayPal') and post_amount == 1:
 
            fallback_key = 'paypal-id'
conservancy_beancount/plugin/meta_receivable_documentation.py
Show inline comments
...
 
@@ -57,7 +57,7 @@ class MetaReceivableDocumentation(core._RequireLinksPostingMetadataHook):
 
        self.rt = rt_wrapper
 

	
 
    def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
 
        if not post.account.is_under('Accrued:AccountsReceivable'):
 
        if not post.account.is_under('Assets:Receivable'):
 
            return False
 

	
 
        # Get the first invoice, or return False if it doesn't exist.
tests/test_data_account.py
Show inline comments
...
 
@@ -26,55 +26,46 @@ from conservancy_beancount import data
 
    ('Expenses:Tax:Sales', 'Expenses:', True),
 
    ('Expenses:Tax:Sales', 'Expenses', True),
 
    ('Expenses:Tax:Sales', 'Expense', False),
 
    ('Expenses:Tax:Sales', 'Accrued:', False),
 
    ('Expenses:Tax:Sales', 'Accrued', False),
 
    ('Expenses:Tax:Sales', 'Equity:', False),
 
    ('Expenses:Tax:Sales', 'Equity', False),
 
])
 
def test_is_under_one_arg(acct_name, under_arg, expected):
 
    expected = under_arg if expected else None
 
    assert data.Account(acct_name).is_under(under_arg) == expected
 

	
 
@pytest.mark.parametrize('acct_name,expected', [
 
    ('Income:Other', 'Income'),
 
    ('UnearnedIncome:Other', 'UnearnedIncome'),
 
    ('Accrued:AccountsPayable', None),
 
    ('Expenses:General', None),
 
    ('Assets:Cash', None),
 
    ('Assets:Checking', None),
 
    ('Assets:Prepaid:Expenses', 'Assets:Prepaid'),
 
    ('Assets:Receivable:Accounts', 'Assets:Receivable'),
 
])
 
def test_is_under_multi_arg(acct_name, expected):
 
    assert data.Account(acct_name).is_under('Income', 'UnearnedIncome') == expected
 
    assert expected == data.Account(acct_name).is_under(
 
        'Assets:Prepaid', 'Assets:Receivable',
 
    )
 
    if expected:
 
        expected += ':'
 
    assert data.Account(acct_name).is_under('Income:', 'UnearnedIncome:') == expected
 
    assert expected == data.Account(acct_name).is_under(
 
        'Assets:Prepaid:', 'Assets:Receivable:',
 
    )
 

	
 
@pytest.mark.parametrize('acct_name,expected', [
 
    ('Accrued:AccountsReceivable', False),
 
    ('Assets:Cash', False),
 
    ('Expenses:General', False),
 
    ('Income:Donations', True),
 
    ('Income:Sales', True),
 
    ('Income:Other', True),
 
    ('Liabilities:CreditCard', False),
 
    ('UnearnedIncome:MatchPledges', True),
 
])
 
def test_is_income(acct_name, expected):
 
    assert data.Account(acct_name).is_income() == expected
 

	
 
@pytest.mark.parametrize('acct_name,expected', [
 
    ('Accrued:AccountsPayable', False),
 
    ('Accrued:AccountsReceivable', False),
 
    ('Assets:Bank:Checking', True),
 
    ('Assets:Cash', True),
 
    ('Assets:Cash:EUR', True),
 
    ('Assets:PrepaidExpenses', False),
 
    ('Assets:PrepaidVacation', False),
 
    ('Assets:Bank:Checking', True),
 
    ('Expenses:General', False),
 
    ('Income:Donations', False),
 
    ('Assets:Prepaid:Expenses', False),
 
    ('Assets:Prepaid:Vacation', False),
 
    ('Assets:Receivable:Accounts', False),
 
    ('Assets:Receivable:Fraud', False),
 
    ('Expenses:Other', False),
 
    ('Equity:OpeningBalance', False),
 
    ('Income:Other', False),
 
    ('Liabilities:CreditCard', False),
 
])
 
def test_is_real_asset(acct_name, expected):
 
    assert data.Account(acct_name).is_real_asset() == expected
 
def test_is_cash_equivalent(acct_name, expected):
 
    assert data.Account(acct_name).is_cash_equivalent() == expected
 

	
 
@pytest.mark.parametrize('acct_name,expected', [
 
    ('Accrued:AccountsPayable', False),
 
    ('Accrued:AccountsReceivable', False),
 
    ('Assets:Bank:Check9999', True),
 
    ('Assets:Bank:CheckCard', True),
 
    ('Assets:Bank:Checking', True),
...
 
@@ -83,10 +74,27 @@ def test_is_real_asset(acct_name, expected):
 
    ('Assets:Check9999', True),
 
    ('Assets:CheckCard', True),
 
    ('Assets:Checking', True),
 
    ('Assets:PrepaidExpenses', False),
 
    ('Assets:Savings', False),
 
    ('Expenses:CheckingFees', False),
 
    ('Income:Interest:Checking', False),
 
    ('Assets:Prepaid:Expenses', False),
 
    ('Assets:Receivable:Accounts', False),
 
    ('Expenses:Other', False),
 
    ('Equity:OpeningBalance', False),
 
    ('Income:Other', False),
 
    ('Liabilities:CreditCard', False),
 
])
 
def test_is_checking(acct_name, expected):
 
    assert data.Account(acct_name).is_checking() == expected
 

	
 
@pytest.mark.parametrize('acct_name,expected', [
 
    ('Assets:Cash', False),
 
    ('Assets:Prepaid:Expenses', False),
 
    ('Assets:Receivable:Accounts', False),
 
    ('Expenses:Other', False),
 
    ('Equity:OpeningBalance', False),
 
    ('Income:Other', False),
 
    ('Liabilities:CreditCard', True),
 
    ('Liabilities:CreditCard:Visa', True),
 
    ('Liabilities:Payable:Accounts', False),
 
    ('Liabilities:UnearnedIncome:Donations', False),
 
])
 
def test_is_credit_card(acct_name, expected):
 
    assert data.Account(acct_name).is_credit_card() == expected
tests/test_data_posting.py
Show inline comments
...
 
@@ -26,17 +26,17 @@ from conservancy_beancount import data
 

	
 
PAYMENT_ACCOUNTS = {
 
    'Assets:Cash',
 
    'Assets:Checking',
 
    'Assets:Bank:Checking',
 
}
 

	
 
NON_PAYMENT_ACCOUNTS = {
 
    'Accrued:AccountsReceivable',
 
    'Assets:PrepaidExpenses',
 
    'Assets:PrepaidVacation',
 
    'Assets:Prepaid:Expenses',
 
    'Assets:Prepaid:Vacation',
 
    'Assets:Receivable:Accounts',
 
    'Equity:OpeningBalance',
 
    'Expenses:Other',
 
    'Income:Other',
 
    'Liabilities:CreditCard',
 
    'UnearnedIncome:MatchPledges',
 
}
 

	
 
AMOUNTS = [
tests/test_meta_approval.py
Show inline comments
...
 
@@ -21,18 +21,18 @@ from . import testutil
 
from conservancy_beancount.plugin import meta_approval
 

	
 
REQUIRED_ACCOUNTS = {
 
    'Assets:Bank:Checking',
 
    'Assets:Cash',
 
    'Assets:Checking',
 
    'Assets:Savings',
 
}
 

	
 
NON_REQUIRED_ACCOUNTS = {
 
    'Accrued:AccountsPayable',
 
    'Assets:PrepaidExpenses',
 
    'Assets:PrepaidVacation',
 
    'Assets:Prepaid:Expenses',
 
    'Assets:Receivable:Accounts',
 
    'Equity:QpeningBalance',
 
    'Expenses:Other',
 
    'Income:Other',
 
    'UnearnedIncome:Donations',
 
    'Liabilities:Payable:Accounts',
 
}
 

	
 
CREDITCARD_ACCOUNT = 'Liabilities:CreditCard'
tests/test_meta_entity.py
Show inline comments
...
 
@@ -111,17 +111,21 @@ def test_invalid_values_on_transactions(hook, src_value):
 
               for error in hook.run(txn))
 

	
 
@pytest.mark.parametrize('account,required', [
 
    ('Accrued:AccountsReceivable', True),
 
    ('Assets:Bank:Checking', False),
 
    ('Assets:Cash', False),
 
    ('Assets:Receivable:Accounts', True),
 
    ('Assets:Receivable:Loans', True),
 
    ('Equity:OpeningBalances', False),
 
    ('Expenses:General', True),
 
    ('Income:Donations', True),
 
    ('Liabilities:CreditCard', False),
 
    ('UnearnedIncome:Donations', True),
 
    ('Liabilities:Payable:Accounts', True),
 
    ('Liabilities:Payable:Vacation', True),
 
    ('Liabilities:UnearnedIncome:Donations', False),
 
])
 
def test_which_accounts_required_on(hook, account, required):
 
    txn = testutil.Transaction(postings=[
 
        ('Assets:Checking', 25),
 
        ('Assets:Checking', -25),
 
        (account, 25),
 
    ])
 
    errors = list(hook.run(txn))
tests/test_meta_expense_allocation.py
Show inline comments
...
 
@@ -83,11 +83,12 @@ def test_invalid_values_on_transactions(hook, src_value):
 
    testutil.check_post_meta(txn, None, None)
 

	
 
@pytest.mark.parametrize('account', [
 
    'Accrued:AccountsReceivable',
 
    'Assets:Cash',
 
    'Income:Donations',
 
    'Assets:Receivable:Accounts',
 
    'Equity:OpeningBalance',
 
    'Income:Other',
 
    'Liabilities:CreditCard',
 
    'UnearnedIncome:Donations',
 
    'Liabilities:Payable:Vacation',
 
])
 
def test_non_expense_accounts_skipped(hook, account):
 
    meta = {TEST_KEY: 'program'}
tests/test_meta_income_type.py
Show inline comments
...
 
@@ -83,10 +83,12 @@ def test_invalid_values_on_transactions(hook, src_value):
 
    testutil.check_post_meta(txn, None, None)
 

	
 
@pytest.mark.parametrize('account', [
 
    'Accrued:AccountsReceivable',
 
    'Assets:Cash',
 
    'Expenses:General',
 
    'Assets:Receivable:Accounts',
 
    'Equity:OpeningBalance',
 
    'Expenses:Other',
 
    'Liabilities:CreditCard',
 
    'Liabilities:Payable:Vacation',
 
])
 
def test_non_income_accounts_skipped(hook, account):
 
    meta = {TEST_KEY: 'RBI'}
...
 
@@ -99,6 +101,8 @@ def test_non_income_accounts_skipped(hook, account):
 
    testutil.check_post_meta(txn, None, meta)
 

	
 
@pytest.mark.parametrize('account,set_value', [
 
    ('Income:Conferences:Registrations', 'RBI'),
 
    ('Income:Conferences:Sponsorship', 'RBI'),
 
    ('Income:Donations', 'Donations'),
 
    ('Income:Honoraria', 'RBI'),
 
    ('Income:Interest', 'RBI'),
...
 
@@ -107,8 +111,6 @@ def test_non_income_accounts_skipped(hook, account):
 
    ('Income:Sales', 'RBI'),
 
    ('Income:SoftwareDevelopment', 'RBI'),
 
    ('Income:TrademarkLicensing', 'RBI'),
 
    ('UnearnedIncome:Conferences:Registrations', 'RBI'),
 
    ('UnearnedIncome:MatchPledges', 'Donations'),
 
])
 
def test_default_values(hook, account, set_value):
 
    txn = testutil.Transaction(postings=[
tests/test_meta_invoice.py
Show inline comments
...
 
@@ -21,16 +21,18 @@ from . import testutil
 
from conservancy_beancount.plugin import meta_invoice
 

	
 
REQUIRED_ACCOUNTS = {
 
    'Accrued:AccountsPayable',
 
    'Accrued:AccountsReceivable',
 
    'Assets:Receivable:Accounts',
 
    'Assets:Receivable:Loans',
 
    'Liabilities:Payable:Accounts',
 
    'Liabilities:Payable:Vacation',
 
}
 

	
 
NON_REQUIRED_ACCOUNTS = {
 
    'Assets:Cash',
 
    'Equity:OpeningBalance',
 
    'Expenses:Other',
 
    'Income:Other',
 
    'Liabilities:CreditCard',
 
    'UnearnedIncome:Donations',
 
}
 

	
 
TEST_KEY = 'invoice'
tests/test_meta_project.py
Show inline comments
...
 
@@ -86,13 +86,17 @@ def test_invalid_values_on_transactions(hook, src_value):
 
    testutil.check_post_meta(txn, None, None)
 

	
 
@pytest.mark.parametrize('account,required', [
 
    ('Accrued:AccountsReceivable', True),
 
    ('Assets:Cash', False),
 
    ('Equity:Opening-Balances', False),
 
    ('Assets:Receivable:Accounts', True),
 
    ('Assets:Receivable:Loans', True),
 
    ('Equity:OpeningBalance', False),
 
    ('Expenses:General', True),
 
    ('Income:Donations', True),
 
    ('Liabilities:CreditCard', False),
 
    ('UnearnedIncome:Donations', True),
 
    ('Liabilities:Payable:Accounts', True),
 
    # We do want a "project" for Lia:Pay:Vacation but it has a default value
 
    ('Liabilities:Payable:Vacation', False),
 
    ('Liabilities:UnearnedIncome:Donations', True),
 
])
 
def test_which_accounts_required_on(hook, account, required):
 
    txn = testutil.Transaction(postings=[
...
 
@@ -103,9 +107,9 @@ def test_which_accounts_required_on(hook, account, required):
 
    assert required == any(errors)
 

	
 
@pytest.mark.parametrize('account', [
 
    'Accrued:VacationPayable',
 
    'Expenses:Payroll:Salary',
 
    'Expenses:Payroll:Tax',
 
    'Liabilities:Payable:Vacation',
 
])
 
def test_default_values(hook, account):
 
    txn = testutil.Transaction(postings=[
...
 
@@ -126,7 +130,7 @@ def test_default_values(hook, account):
 
def test_default_value_set_in_date_range(hook, date, required):
 
    txn = testutil.Transaction(date=date, postings=[
 
        ('Expenses:Payroll:Benefits', 25),
 
        ('Accrued:VacationPayable', -25),
 
        ('Liabilities:Payable:Vacation', -25),
 
    ])
 
    errors = list(hook.run(txn))
 
    assert not errors
tests/test_meta_receipt.py
Show inline comments
...
 
@@ -77,13 +77,13 @@ KNOWN_FALLBACKS = {acct.fallback_meta for acct in ACCOUNTS_WITH_FALLBACKS}
 
# doesn't require the decorated test to go over every value, which in turn
 
# trims unnecessary test time.
 
NOT_REQUIRED_ACCOUNTS = itertools.cycle([
 
    'Accrued:AccountsPayable',
 
    'Accrued:AccountsReceivable',
 
    'Assets:PrepaidExpenses',
 
    'Assets:PrepaidVacation',
 
    'Assets:Prepaid:Expenses',
 
    'Assets:Receivable:Accounts',
 
    'Equity:OpeningBalance',
 
    'Expenses:Other',
 
    'Income:Other',
 
    'UnearnedIncome:Donations',
 
    'Liabilities:Payable:Accounts',
 
    'Liabilities:UnearnedIncome:Donations',
 
])
 

	
 
def check(hook, test_acct, other_acct, expected, *,
tests/test_meta_receivable_documentation.py
Show inline comments
...
 
@@ -23,7 +23,7 @@ from . import testutil
 
from conservancy_beancount import errors as errormod
 
from conservancy_beancount.plugin import meta_receivable_documentation
 

	
 
TEST_ACCT = 'Accrued:AccountsReceivable'
 
TEST_ACCT = 'Assets:Receivable:Accounts'
 
OTHER_ACCT = 'Income:Donations'
 

	
 
SUPPORTING_METADATA = [
...
 
@@ -178,9 +178,17 @@ def test_type_errors_reported_with_valid_txn_docs(hook, invoice, support_key, su
 
def test_received_invoices_not_checked(hook, invoice, meta_type):
 
    check(hook, None, **{meta_type: {'invoice': invoice}})
 

	
 
def test_does_not_apply_to_payables(hook):
 
@pytest.mark.parametrize('account', [
 
    'Assets:Bank:Checking',
 
    'Assets:Cash',
 
    'Equity:OpeningBalance',
 
    'Expenses:BankingFees',
 
    'Liabilities:CreditCard',
 
    'Liabilities:Payable:Accounts',
 
])
 
def test_does_not_apply_to_other_accounts(hook, account):
 
    meta = seed_meta()
 
    check(hook, None, 'Accrued:AccountsPayable', 'Expenses:Other', post_meta=meta)
 
    check(hook, None, account, 'Expenses:Other', post_meta=meta)
 

	
 
def test_configuration_error_without_rt():
 
    config = testutil.TestConfig()
tests/test_meta_tax_implication.py
Show inline comments
...
 
@@ -57,7 +57,7 @@ def hook():
 
@pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items())
 
def test_valid_values_on_postings(hook, src_value, set_value):
 
    txn = testutil.Transaction(postings=[
 
        ('Accrued:AccountsPayable', 25),
 
        ('Liabilities:Payable:Accounts', 25),
 
        ('Assets:Cash', -25, {TEST_KEY: src_value}),
 
    ])
 
    errors = list(hook.run(txn))
...
 
@@ -67,7 +67,7 @@ def test_valid_values_on_postings(hook, src_value, set_value):
 
@pytest.mark.parametrize('src_value', INVALID_VALUES)
 
def test_invalid_values_on_postings(hook, src_value):
 
    txn = testutil.Transaction(postings=[
 
        ('Accrued:AccountsPayable', 25),
 
        ('Liabilities:Payable:Accounts', 25),
 
        ('Assets:Cash', -25, {TEST_KEY: src_value}),
 
    ])
 
    errors = list(hook.run(txn))
...
 
@@ -77,7 +77,7 @@ def test_invalid_values_on_postings(hook, src_value):
 
@pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items())
 
def test_valid_values_on_transactions(hook, src_value, set_value):
 
    txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[
 
        ('Accrued:AccountsPayable', 25),
 
        ('Liabilities:Payable:Accounts', 25),
 
        ('Assets:Cash', -25),
 
    ])
 
    errors = list(hook.run(txn))
...
 
@@ -87,37 +87,34 @@ def test_valid_values_on_transactions(hook, src_value, set_value):
 
@pytest.mark.parametrize('src_value', INVALID_VALUES)
 
def test_invalid_values_on_transactions(hook, src_value):
 
    txn = testutil.Transaction(**{TEST_KEY: src_value}, postings=[
 
        ('Accrued:AccountsPayable', 25),
 
        ('Liabilities:Payable:Accounts', 25),
 
        ('Assets:Cash', -25),
 
    ])
 
    errors = list(hook.run(txn))
 
    assert errors
 
    testutil.check_post_meta(txn, None, None)
 

	
 
@pytest.mark.parametrize('account', [
 
    'Accrued:AccountsPayable',
 
    'Expenses:General',
 
@pytest.mark.parametrize('count,account', enumerate([
 
    'Assets:Payable:Accounts',
 
    'Assets:Prepaid:Expenses',
 
    'Equity:OpeningBalance',
 
    'Expenses:Other',
 
    'Income:Other',
 
    'Liabilities:CreditCard',
 
])
 
def test_non_asset_accounts_skipped(hook, account):
 
    'Liabilities:Payable:Accounts',
 
    'Liabilities:UnearnedIncome:Donations',
 
], 1))
 
def test_non_payment_accounts_skipped(hook, account, count):
 
    amount = count * 100
 
    meta = {TEST_KEY: 'USA-Corporation'}
 
    txn = testutil.Transaction(postings=[
 
        (account, 25),
 
        ('Assets:Cash', -25, meta.copy()),
 
        (account, amount),
 
        ('Assets:Checking', -amount, meta.copy()),
 
    ])
 
    errors = list(hook.run(txn))
 
    assert not errors
 
    testutil.check_post_meta(txn, None, meta)
 

	
 
def test_prepaid_expenses_skipped(hook, ):
 
    txn = testutil.Transaction(postings=[
 
        ('Expenses:General', 25),
 
        ('Assets:PrepaidExpenses', -25),
 
    ])
 
    errors = list(hook.run(txn))
 
    assert not errors
 
    testutil.check_post_meta(txn, None, None)
 

	
 
def test_asset_credits_skipped(hook, ):
 
    txn = testutil.Transaction(postings=[
 
        ('Income:Donations', -25),
0 comments (0 inline, 0 general)