accrual: Check the consistency of accruals' cost.
This commit is contained in:
parent
a008a09477
commit
aef00ce83f
3 changed files with 34 additions and 10 deletions
|
@ -67,6 +67,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from typing import (
|
from typing import (
|
||||||
|
Any,
|
||||||
Callable,
|
Callable,
|
||||||
Dict,
|
Dict,
|
||||||
Iterable,
|
Iterable,
|
||||||
|
@ -328,19 +329,28 @@ class SearchTerm(NamedTuple):
|
||||||
)
|
)
|
||||||
return cls(key, pattern)
|
return cls(key, pattern)
|
||||||
|
|
||||||
|
def _consistency_check_one_thing(
|
||||||
|
key: MetaValue,
|
||||||
|
related: core.RelatedPostings,
|
||||||
|
get_name: str,
|
||||||
|
get_func: Callable[[data.Posting], Any],
|
||||||
|
) -> Iterable[Error]:
|
||||||
|
values = {get_func(post) for post in related}
|
||||||
|
if len(values) != 1:
|
||||||
|
for post in related:
|
||||||
|
errmsg = f'inconsistent {get_name} for invoice {key}: {get_func(post)}'
|
||||||
|
yield Error(post.meta, errmsg, post.meta.txn)
|
||||||
|
|
||||||
def consistency_check(groups: PostGroups) -> Iterable[Error]:
|
def consistency_check(groups: PostGroups) -> Iterable[Error]:
|
||||||
|
errfmt = 'inconsistent {} for invoice {}: {{}}'
|
||||||
for key, related in groups.items():
|
for key, related in groups.items():
|
||||||
|
yield from _consistency_check_one_thing(
|
||||||
|
key, related, 'cost', lambda post: post.cost,
|
||||||
|
)
|
||||||
for checked_meta in ['contract', 'entity', 'purchase-order']:
|
for checked_meta in ['contract', 'entity', 'purchase-order']:
|
||||||
meta_values = related.meta_values(checked_meta)
|
yield from _consistency_check_one_thing(
|
||||||
if len(meta_values) != 1:
|
key, related, checked_meta, lambda post: post.meta.get(checked_meta),
|
||||||
errmsg = f'inconsistent {checked_meta} for invoice {key}'
|
)
|
||||||
for post in related:
|
|
||||||
yield Error(
|
|
||||||
post.meta,
|
|
||||||
f'{errmsg}: {post.meta.get(checked_meta)}',
|
|
||||||
post.meta.txn,
|
|
||||||
)
|
|
||||||
|
|
||||||
def filter_search(postings: Iterable[data.Posting],
|
def filter_search(postings: Iterable[data.Posting],
|
||||||
search_terms: Iterable[SearchTerm],
|
search_terms: Iterable[SearchTerm],
|
||||||
|
|
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.0.8',
|
version='1.0.9',
|
||||||
author='Software Freedom Conservancy',
|
author='Software Freedom Conservancy',
|
||||||
author_email='info@sfconservancy.org',
|
author_email='info@sfconservancy.org',
|
||||||
license='GNU AGPLv3+',
|
license='GNU AGPLv3+',
|
||||||
|
|
|
@ -250,6 +250,20 @@ def test_consistency_check_when_inconsistent(meta_key, account):
|
||||||
assert actual.entry is txn
|
assert actual.entry is txn
|
||||||
assert actual.source.get('lineno') == exp_lineno
|
assert actual.source.get('lineno') == exp_lineno
|
||||||
|
|
||||||
|
def test_consistency_check_cost():
|
||||||
|
account = ACCOUNTS[0]
|
||||||
|
invoice = 'test-cost-invoice'
|
||||||
|
txn = testutil.Transaction(postings=[
|
||||||
|
(account, 100, 'EUR', ('1.1251', 'USD'), {'invoice': invoice, 'lineno': 1}),
|
||||||
|
(account, -100, 'EUR', ('1.125', 'USD'), {'invoice': invoice, 'lineno': 2}),
|
||||||
|
])
|
||||||
|
related = core.RelatedPostings(data.Posting.from_txn(txn))
|
||||||
|
errors = list(accrual.consistency_check({invoice: related}))
|
||||||
|
for post, err in itertools.zip_longest(txn.postings, errors):
|
||||||
|
assert err.message == f'inconsistent cost for invoice {invoice}: {post.cost}'
|
||||||
|
assert err.entry is txn
|
||||||
|
assert err.source.get('lineno') == post.meta['lineno']
|
||||||
|
|
||||||
def check_output(output, expect_patterns):
|
def check_output(output, expect_patterns):
|
||||||
output.seek(0)
|
output.seek(0)
|
||||||
testutil.check_lines_match(iter(output), expect_patterns)
|
testutil.check_lines_match(iter(output), expect_patterns)
|
||||||
|
|
Loading…
Reference in a new issue