From dbe9362987c0c26e1ca4db1c5806807a0860df5c 2020-03-05 20:48:10 From: Brett Smith Date: 2020-03-05 20:48:10 Subject: [PATCH] meta_tax_implication: Initial module and tests. --- diff --git a/conservancy_beancount/plugin/meta_tax_implication.py b/conservancy_beancount/plugin/meta_tax_implication.py new file mode 100644 index 0000000000000000000000000000000000000000..fb6226d2ac18c3f265d0f5d0bc4026c6f70d7cc7 --- /dev/null +++ b/conservancy_beancount/plugin/meta_tax_implication.py @@ -0,0 +1,50 @@ +"""meta_tax_implication - Validate taxImplication metadata""" +# 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 . + +import decimal + +from . import core + +DEFAULT_STOP_AMOUNT = decimal.Decimal(0) + +class MetaTaxImplication(core.PostingChecker): + ACCOUNTS = ('Assets:',) + METADATA_KEY = 'taxImplication' + VALUES_ENUM = core.MetadataEnum(METADATA_KEY, [ + '1099', + 'Accountant-Advises-No-1099', + 'Bank-Transfer', + 'Foreign-Corporation', + 'Foreign-Individual-Contractor', + 'Fraud', + 'HSA-Contribution', + 'Loan', + 'Payroll', + 'Refund', + 'Reimbursement', + 'Retirement-Pretax', + 'Tax-Payment', + 'USA-501c3', + 'USA-Corporation', + 'USA-LLC-No-1099', + 'W2', + ], {}) + + def _should_check(self, txn, post): + return ( + super()._should_check(txn, post) + and post.units.number < DEFAULT_STOP_AMOUNT + ) diff --git a/tests/test_meta_taxImplication.py b/tests/test_meta_taxImplication.py new file mode 100644 index 0000000000000000000000000000000000000000..1b1cf8a6410ac7d825aa354b8f65cba23a527a6c --- /dev/null +++ b/tests/test_meta_taxImplication.py @@ -0,0 +1,130 @@ +"""Test handling of taxImplication metadata""" +# 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 . + +import pytest + +from . import testutil + +from conservancy_beancount.plugin import meta_tax_implication + +VALID_VALUES = { + '1099': '1099', + 'Accountant-Advises-No-1099': 'Accountant-Advises-No-1099', + 'Bank-Transfer': 'Bank-Transfer', + 'Foreign-Corporation': 'Foreign-Corporation', + 'Foreign-Individual-Contractor': 'Foreign-Individual-Contractor', + 'Fraud': 'Fraud', + 'HSA-Contribution': 'HSA-Contribution', + 'Loan': 'Loan', + 'Payroll': 'Payroll', + 'Refund': 'Refund', + 'Reimbursement': 'Reimbursement', + 'Retirement-Pretax': 'Retirement-Pretax', + 'Tax-Payment': 'Tax-Payment', + 'USA-501c3': 'USA-501c3', + 'USA-Corporation': 'USA-Corporation', + 'USA-LLC-No-1099': 'USA-LLC-No-1099', + 'W2': 'W2', +} + +INVALID_VALUES = { + '199', + 'W3', + 'Payrol', + '', +} + +@pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) +def test_valid_values_on_postings(src_value, set_value): + txn = testutil.Transaction(postings=[ + ('Accrued:AccountsPayable', 25), + ('Assets:Cash', -25, {'taxImplication': src_value}), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert not errors + assert txn.postings[-1].meta.get('taxImplication') == set_value + +@pytest.mark.parametrize('src_value', INVALID_VALUES) +def test_invalid_values_on_postings(src_value): + txn = testutil.Transaction(postings=[ + ('Accrued:AccountsPayable', 25), + ('Assets:Cash', -25, {'taxImplication': src_value}), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert errors + +@pytest.mark.parametrize('src_value,set_value', VALID_VALUES.items()) +def test_valid_values_on_transactions(src_value, set_value): + txn = testutil.Transaction(taxImplication=src_value, postings=[ + ('Accrued:AccountsPayable', 25), + ('Assets:Cash', -25), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert not errors + assert txn.postings[-1].meta.get('taxImplication') == set_value + +@pytest.mark.parametrize('src_value', INVALID_VALUES) +def test_invalid_values_on_transactions(src_value): + txn = testutil.Transaction(taxImplication=src_value, postings=[ + ('Accrued:AccountsPayable', 25), + ('Assets:Cash', -25), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert errors + +@pytest.mark.parametrize('account', [ + 'Accrued:AccountsPayable', + 'Expenses:General', + 'Liabilities:CreditCard', +]) +def test_non_asset_accounts_skipped(account): + txn = testutil.Transaction(postings=[ + (account, 25), + ('Assets:Cash', -25, {'taxImplication': 'USA-Corporation'}), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[0]) + assert not errors + +def test_asset_credits_skipped(): + txn = testutil.Transaction(postings=[ + ('Income:Donations', -25), + ('Assets:Cash', 25), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert not errors + assert not txn.postings[-1].meta + +@pytest.mark.parametrize('date,need_value', [ + (testutil.EXTREME_FUTURE_DATE, False), + (testutil.FUTURE_DATE, True), + (testutil.FY_START_DATE, True), + (testutil.FY_MID_DATE, True), + (testutil.PAST_DATE, False), +]) +def test_default_value_set_in_date_range(date, need_value): + txn = testutil.Transaction(date=date, postings=[ + ('Liabilites:CreditCard', 25), + ('Assets:Cash', -25), + ]) + checker = meta_tax_implication.MetaTaxImplication() + errors = checker.check(txn, txn.postings[-1]) + assert bool(errors) == bool(need_value)