plugin: Don't validate transactions flagged with !. RT#10591.

This commit is contained in:
Brett Smith 2020-05-11 09:52:05 -04:00
parent 56b644f1db
commit 536b50b478
10 changed files with 77 additions and 17 deletions

View file

@ -71,10 +71,6 @@ class Hook(Generic[Entry], metaclass=abc.ABCMeta):
def run(self, entry: Entry) -> errormod.Iter: ...
class TransactionHook(Hook[Transaction]):
DIRECTIVE = Transaction
### HELPER CLASSES
class LessComparable(metaclass=abc.ABCMeta):
@ -178,18 +174,28 @@ class MetadataEnum:
### HOOK SUBCLASSES
class _PostingHook(TransactionHook, metaclass=abc.ABCMeta):
class TransactionHook(Hook[Transaction]):
DIRECTIVE = Transaction
TXN_DATE_RANGE: _GenericRange = _GenericRange(DEFAULT_START_DATE, DEFAULT_STOP_DATE)
def __init_subclass__(cls) -> None:
cls.HOOK_GROUPS = cls.HOOK_GROUPS.union(['posting'])
def _run_on_txn(self, txn: Transaction) -> bool:
"""Check whether we should run on a given transaction
This method implements our usual checks for whether or not a hook
should run on a given transaction. It's here for subclasses to use in
their own implementations. See _PostingHook below for an example.
"""
return (
txn.date in self.TXN_DATE_RANGE
txn.flag != '!'
and txn.date in self.TXN_DATE_RANGE
and not data.is_opening_balance_txn(txn)
)
class _PostingHook(TransactionHook, metaclass=abc.ABCMeta):
def __init_subclass__(cls) -> None:
cls.HOOK_GROUPS = cls.HOOK_GROUPS.union(['posting'])
def _run_on_post(self, txn: Transaction, post: data.Posting) -> bool:
return True

View file

@ -63,7 +63,8 @@ class MetaRepoLinks(core.TransactionHook):
yield errormod.BrokenLinkError(txn, key, link)
def run(self, txn: Transaction) -> errormod.Iter:
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
if post.meta is not None:
yield from self._check_links(post.meta, txn, post)
if self._run_on_txn(txn):
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
if post.meta is not None:
yield from self._check_links(post.meta, txn, post)

View file

@ -59,7 +59,8 @@ class MetaRTLinks(core.TransactionHook):
yield errormod.BrokenRTLinkError(txn, key, link, parsed)
def run(self, txn: Transaction) -> errormod.Iter:
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
if post.meta is not None:
yield from self._check_links(post.meta, txn, post)
if self._run_on_txn(txn):
yield from self._check_links(txn.meta, txn)
for post in txn.postings:
if post.meta is not None:
yield from self._check_links(post.meta, txn, post)

View file

@ -180,3 +180,10 @@ def test_approval_required_for_partial_transfer(hook):
])
actual = {error.message for error in hook.run(txn)}
assert actual == {"Assets:Checking missing {}".format(TEST_KEY)}
def test_not_required_on_flagged(hook):
txn = testutil.Transaction(flag='!', postings=[
('Assets:Checking', -25),
('Liabilities:Payable:Accounts', 25),
])
assert not list(hook.run(txn))

View file

@ -144,3 +144,14 @@ def test_missing_invoice(hook, acct1, acct2):
def test_not_required_on_opening(hook):
txn = testutil.OpeningBalance()
assert not list(hook.run(txn))
@pytest.mark.parametrize('acct1,acct2', testutil.combine_values(
REQUIRED_ACCOUNTS,
NON_REQUIRED_ACCOUNTS,
))
def test_not_required_on_flagged(acct1, acct2, hook):
txn = testutil.Transaction(flag='!', postings=[
(acct1, 25),
(acct2, -25),
])
assert not list(hook.run(txn))

View file

@ -171,3 +171,6 @@ def test_not_required_on_opening(hook):
(next(testutil.OPENING_EQUITY_ACCOUNTS), 40),
])
assert not list(hook.run(txn))
def test_not_required_on_flagged(hook):
check(hook, None, txn_meta={'flag': '!'})

View file

@ -349,3 +349,10 @@ def test_fallback_on_zero_amount_postings(hook, test_acct, other_acct, value):
))
def test_not_required_on_opening(hook, test_acct, equity_acct):
check(hook, test_acct, equity_acct, None)
@pytest.mark.parametrize('test_acct,other_acct', testutil.combine_values(
ACCOUNTS,
NOT_REQUIRED_ACCOUNTS,
))
def test_not_required_on_flagged(hook, test_acct, other_acct):
check(hook, test_acct, other_acct, None, txn_meta={'flag': '!'})

View file

@ -213,3 +213,7 @@ def test_not_required_on_opening(hook):
(next(testutil.OPENING_EQUITY_ACCOUNTS), -300),
])
assert not list(hook.run(txn))
def test_not_required_on_flagged(hook):
post_meta = seed_meta()
check(hook, None, txn_meta={'flag': '!'}, post_meta=post_meta)

View file

@ -100,6 +100,16 @@ def test_bad_post_links(hook):
actual = {error.message for error in hook.run(txn)}
assert expected == actual
def test_flagged_txn_not_checked(hook):
keys = iter(METADATA_KEYS)
txn_meta = build_meta(keys, BAD_LINKS)
txn_meta['flag'] = '!'
txn = testutil.Transaction(**txn_meta, postings=[
('Income:Donations', -5, build_meta(keys, BAD_LINKS)),
('Assets:Checking', 5, build_meta(keys, BAD_LINKS)),
])
assert not list(hook.run(txn))
@pytest.mark.parametrize('value', testutil.NON_STRING_METADATA_VALUES)
def test_bad_metadata_type(hook, value):
txn = testutil.Transaction(**{'check': value}, postings=[

View file

@ -146,6 +146,16 @@ def test_docs_outside_rt_not_checked(hook, ext_doc):
actual = {error.message for error in hook.run(txn)}
assert expected == actual
def test_flagged_txn_not_checked(hook):
txn_meta = build_meta(None, MALFORMED_LINKS)
txn_meta['flag'] = '!'
keys = iter(METADATA_KEYS)
txn = testutil.Transaction(**txn_meta, postings=[
('Income:Donations', -5, build_meta(keys, MALFORMED_LINKS)),
('Assets:Checking', 5, build_meta(keys, NOT_FOUND_LINKS)),
])
assert not list(hook.run(txn))
def test_mixed_results(hook):
txn = testutil.Transaction(
approval='{} {}'.format(*GOOD_LINKS),