Changeset - 0b3eb1d1d377
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-05 13:10:48
brettcsmith@brettcsmith.org
accrual: Inconsistent entity is not an error.
3 files changed with 85 insertions and 14 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -96,2 +96,3 @@ from typing import (
 
    Tuple,
 
    TypeVar,
 
    Union,
...
 
@@ -123,2 +124,3 @@ STANDARD_PATH = Path('-')
 

	
 
CompoundAmount = TypeVar('CompoundAmount', data.Amount, core.Balance)
 
PostGroups = Mapping[Optional[MetaValue], 'AccrualPostings']
...
 
@@ -134,3 +136,3 @@ class Account(NamedTuple):
 
    name: str
 
    norm_func: Callable[[core.Balance], core.Balance]
 
    norm_func: Callable[[CompoundAmount], CompoundAmount]
 
    aging_thresholds: Sequence[int]
...
 
@@ -177,3 +179,2 @@ class AccrualPostings(core.RelatedPostings):
 
        'contract': _meta_getter('contract'),
 
        'entity': _meta_getter('entity'),
 
        'invoice': _meta_getter('invoice'),
...
 
@@ -184,3 +185,5 @@ class AccrualPostings(core.RelatedPostings):
 
        'accrual_type',
 
        'accrued_entities',
 
        'end_balance',
 
        'paid_entities',
 
        'account',
...
 
@@ -189,5 +192,2 @@ class AccrualPostings(core.RelatedPostings):
 
        'contracts',
 
        'entity',
 
        'entitys',
 
        'entities',
 
        'invoice',
...
 
@@ -207,4 +207,2 @@ class AccrualPostings(core.RelatedPostings):
 
        self.account: Union[data.Account, Sentinel]
 
        self.entity: Union[MetaValue, Sentinel]
 
        self.entitys: FrozenSet[MetaValue]
 
        self.invoice: Union[MetaValue, Sentinel]
...
 
@@ -218,4 +216,2 @@ class AccrualPostings(core.RelatedPostings):
 
            setattr(self, name, one_value)
 
        # Correct spelling = bug prevention for future users of this class.
 
        self.entities = self.entitys
 
        if self.account is self.INCONSISTENT:
...
 
@@ -223,5 +219,27 @@ class AccrualPostings(core.RelatedPostings):
 
            self.end_balance = self.balance_at_cost()
 
            self.accrued_entities = self._collect_entities()
 
            self.paid_entities = self.accrued_entities
 
        else:
 
            self.accrual_type = AccrualAccount.classify(self)
 
            self.end_balance = self.accrual_type.value.norm_func(self.balance_at_cost())
 
            norm_func = self.accrual_type.value.norm_func
 
            self.end_balance = norm_func(self.balance_at_cost())
 
            self.accrued_entities = self._collect_entities(
 
                lambda post: norm_func(post.units).number > 0,  # type:ignore[no-any-return]
 
            )
 
            self.paid_entities = self._collect_entities(
 
                lambda post: norm_func(post.units).number < 0,  # type:ignore[no-any-return]
 
            )
 

	
 
    def _collect_entities(self,
 
                          pred: Callable[[data.Posting], bool]=bool,
 
                          default: str='<empty>',
 
    ) -> FrozenSet[MetaValue]:
 
        return frozenset(
 
            post.meta.get('entity') or default
 
            for post in self if pred(post)
 
        )
 

	
 
    def entities(self) -> Iterator[MetaValue]:
 
        yield from self.accrued_entities
 
        yield from self.paid_entities.difference(self.accrued_entities)
 

	
...
 
@@ -441,3 +459,3 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
 
            self.date_cell(row[0].meta.date),
 
            self.multiline_cell(row.entities),
 
            self.multiline_cell(row.entities()),
 
            amount_cell,
...
 
@@ -466,3 +484,3 @@ class AgingReport(BaseReport):
 
            related[0].meta.date,
 
            min(related.entities) if related.entities else '',
 
            min(related.entities()) if related.accrued_entities else '',
 
        ))
tests/books/accruals.beancount
Show inline comments
...
 
@@ -32,2 +32,23 @@
 

	
 
2010-04-15 * "Multiparty invoice"
 
  rt-id: "rt:480"
 
  invoice: "rt:480/4800"
 
  Expenses:Travel  250 USD
 
  Liabilities:Payable:Accounts  -125 USD
 
  entity: "MultiPartyA"
 
  Liabilities:Payable:Accounts  -125 USD
 
  entity: "MultiPartyB"
 

	
 
2010-04-18 * "MultiPartyA" "Payment for 480"
 
  rt-id: "rt:480"
 
  invoice: "rt:480/4800"
 
  Liabilities:Payable:Accounts  125 USD
 
  Assets:Checking  -125 USD
 

	
 
2010-04-20 * "MultiPartyB" "Payment for 480"
 
  rt-id: "rt:480"
 
  invoice: "rt:480/4800"
 
  Liabilities:Payable:Accounts  125 USD
 
  Assets:Checking  -125 USD
 

	
 
2010-04-30 ! "Vendor" "Travel reimbursement"
tests/test_reports_accrual.py
Show inline comments
...
 
@@ -65,3 +65,2 @@ CONSISTENT_METADATA = [
 
    'contract',
 
    'entity',
 
    'purchase-order',
...
 
@@ -356,2 +355,35 @@ def test_accrual_postings_consistent_metadata(meta_key, acct_name):
 

	
 
def test_accrual_postings_entity():
 
    txn = testutil.Transaction(postings=[
 
        (ACCOUNTS[0], 25, {'entity': 'Accruee'}),
 
        (ACCOUNTS[0], -15, {'entity': 'Payee15'}),
 
        (ACCOUNTS[0], -10, {'entity': 'Payee10'}),
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    assert related.accrued_entities == {'Accruee'}
 
    assert related.paid_entities == {'Payee10', 'Payee15'}
 

	
 
def test_accrual_postings_entities():
 
    txn = testutil.Transaction(postings=[
 
        (ACCOUNTS[0], 25, {'entity': 'Accruee'}),
 
        (ACCOUNTS[0], -15, {'entity': 'Payee15'}),
 
        (ACCOUNTS[0], -10, {'entity': 'Payee10'}),
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    actual = related.entities()
 
    assert next(actual, None) == 'Accruee'
 
    assert set(actual) == {'Payee10', 'Payee15'}
 

	
 
def test_accrual_postings_entities_no_duplicates():
 
    txn = testutil.Transaction(postings=[
 
        (ACCOUNTS[0], 25, {'entity': 'Accruee'}),
 
        (ACCOUNTS[0], -15, {'entity': 'Accruee'}),
 
        (ACCOUNTS[0], -10, {'entity': 'Other'}),
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    actual = related.entities()
 
    assert next(actual, None) == 'Accruee'
 
    assert next(actual, None) == 'Other'
 
    assert next(actual, None) is None
 

	
 
def test_accrual_postings_inconsistent_account():
...
 
@@ -401,3 +433,3 @@ def test_consistency_check_when_consistent(meta_key, account):
 
@pytest.mark.parametrize('meta_key,account', testutil.combine_values(
 
    ['approval', 'fx-rate', 'statement'],
 
    ['approval', 'entity', 'fx-rate', 'statement'],
 
    ACCOUNTS,
0 comments (0 inline, 0 general)