Changeset - c88c5ef3b05d
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-04 01:24:47
brettcsmith@brettcsmith.org
accruals: AccrualPostings only reports inconsistent cost per currency.

Of course if an accrual has multiple currencies, it'll probably have
different costs, and that's fine.
3 files changed with 15 insertions and 35 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/accrual.py
Show inline comments
...
 
@@ -141,27 +141,24 @@ class AccrualPostings(core.RelatedPostings):
 
            return post.meta.get(key)
 
        return meta_getter
 

	
 
    _FIELDS: Dict[str, Callable[[data.Posting], MetaValue]] = {
 
        'account': operator.attrgetter('account'),
 
        'contract': _meta_getter('contract'),
 
        'cost': operator.attrgetter('cost'),
 
        'entity': _meta_getter('entity'),
 
        'invoice': _meta_getter('invoice'),
 
        'purchase_order': _meta_getter('purchase-order'),
 
    }
 
    INCONSISTENT = Sentinel()
 
    __slots__ = (
 
        'accrual_type',
 
        'final_bal',
 
        'account',
 
        'accounts',
 
        'contract',
 
        'contracts',
 
        'cost',
 
        'costs',
 
        'entity',
 
        'entitys',
 
        'entities',
 
        'invoice',
 
        'invoices',
 
        'purchase_order',
...
 
@@ -223,12 +220,23 @@ class AccrualPostings(core.RelatedPostings):
 
                    errmsg = 'inconsistent {} for invoice {}: {}'.format(
 
                        field_name.replace('_', '-'),
 
                        self.invoice or "<none>",
 
                        get_func(post),
 
                    )
 
                    yield Error(post.meta, errmsg, post.meta.txn)
 
        costs = collections.defaultdict(set)
 
        for post in self:
 
            costs[post.units.currency].add(post.cost)
 
        for code, currency_costs in costs.items():
 
            if len(currency_costs) > 1:
 
                for post in self:
 
                    if post.units.currency == code:
 
                        errmsg = 'inconsistent cost for invoice {}: {}'.format(
 
                            self.invoice or "<none>", post.cost,
 
                        )
 
                        yield Error(post.meta, errmsg, post.meta.txn)
 

	
 
    def is_paid(self, default: Optional[bool]=None) -> Optional[bool]:
 
        if self.accrual_type is None:
 
            return default
 
        else:
 
            return self.final_bal.le_zero()
tests/books/accruals.beancount
Show inline comments
...
 
@@ -38,14 +38,14 @@
 
  Expenses:Travel  200 USD
 

	
 
2020-05-05 * "DonorA" "Donation pledge"
 
  rt-id: "rt:505"
 
  invoice: "rt:505/5050"
 
  approval: "rt:505/5040"
 
  Income:Donations  -1000.00 USD
 
  Assets:Receivable:Accounts  1000.00 USD
 
  Income:Donations  -2,500 EUR {1.100 USD}
 
  Assets:Receivable:Accounts  2,500 EUR {1.100 USD}
 

	
 
2020-05-10 * "Lawyer" "April legal services"
 
  rt-id: "rt:510"
 
  invoice: "rt:510/5100"
 
  contract: "rt:510/4000"
 
  Expenses:Services:Legal  200.00 USD
...
 
@@ -57,14 +57,14 @@
 
  Income:Donations  -1500.00 USD
 
  Assets:Receivable:Accounts  1500.00 USD
 

	
 
2020-05-20 * "DonorA" "Donation made"
 
  rt-id: "rt:505"
 
  invoice: "rt:505/5050"
 
  Assets:Receivable:Accounts  -1000.00 USD
 
  Assets:Checking  1000.00 USD
 
  Assets:Receivable:Accounts  -2,750.00 USD
 
  Assets:Checking  2,750.00 USD
 
  receipt: "DonorAWire.pdf"
 

	
 
2020-05-25 * "Lawyer" "May payment"
 
  rt-id: "rt:510"
 
  invoice: "rt:510/5100"
 
  Liabilities:Payable:Accounts  200.00 USD
tests/test_reports_accrual.py
Show inline comments
...
 
@@ -209,26 +209,12 @@ def test_accrual_postings_consistent_account(acct_name):
 
        (acct_name, 25, meta),
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    assert related.account == acct_name
 
    assert related.accounts == {acct_name}
 

	
 
@pytest.mark.parametrize('cost', [
 
    testutil.Cost('1.2', 'USD'),
 
    None,
 
])
 
def test_accrual_postings_consistent_cost(cost):
 
    meta = {'invoice': 'FXinvoice.pdf'}
 
    txn = testutil.Transaction(postings=[
 
        (ACCOUNTS[0], 60, 'EUR', cost, meta),
 
        (ACCOUNTS[0], 30, 'EUR', cost, meta),
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    assert related.cost == cost
 
    assert related.costs == {cost}
 

	
 
@pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values(
 
    CONSISTENT_METADATA,
 
    ACCOUNTS,
 
))
 
def test_accrual_postings_consistent_metadata(meta_key, acct_name):
 
    meta_value = f'{meta_key}.pdf'
...
 
@@ -252,26 +238,12 @@ def test_accrual_postings_inconsistent_account():
 
        for index, acct_name in enumerate(ACCOUNTS)
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    assert related.account is related.INCONSISTENT
 
    assert related.accounts == set(ACCOUNTS)
 

	
 
def test_accrual_postings_inconsistent_cost():
 
    meta = {'invoice': 'FXinvoice.pdf'}
 
    costs = {
 
        testutil.Cost('1.1', 'USD'),
 
        testutil.Cost('1.2', 'USD'),
 
    }
 
    txn = testutil.Transaction(postings=[
 
        (ACCOUNTS[0], 10, 'EUR', cost, meta)
 
        for cost in costs
 
    ])
 
    related = accrual.AccrualPostings(data.Posting.from_txn(txn))
 
    assert related.cost is related.INCONSISTENT
 
    assert related.costs == costs
 

	
 
@pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values(
 
    CONSISTENT_METADATA,
 
    ACCOUNTS,
 
))
 
def test_accrual_postings_inconsistent_metadata(meta_key, acct_name):
 
    invoice = 'invoice with {meta_key}.pdf'
0 comments (0 inline, 0 general)