diff --git a/conservancy_beancount/reports/core.py b/conservancy_beancount/reports/core.py index dbf602da6b177c590b209fc3d759b00e90168bb7..d2cc8645de53be47f166c4303b9cad6b5a665e3c 100644 --- a/conservancy_beancount/reports/core.py +++ b/conservancy_beancount/reports/core.py @@ -368,6 +368,14 @@ class RelatedPostings(Sequence[data.Posting]): def balance_at_cost(self) -> Balance: return Balance(post.at_cost() for post in self) + def balance_at_cost_by_date(self, date: datetime.date) -> Balance: + for index, post in enumerate(self): + if post.meta.date >= date: + break + else: + index += 1 + return Balance(post.at_cost() for post in self._postings[:index]) + def meta_values(self, key: MetaKey, default: Optional[MetaValue]=None, diff --git a/tests/test_reports_related_postings.py b/tests/test_reports_related_postings.py index f58a7f5002a93be652f6cf1861db49c08aac8f87..49f0534c8f5516e14004830e0086fe8373d772cd 100644 --- a/tests/test_reports_related_postings.py +++ b/tests/test_reports_related_postings.py @@ -197,6 +197,38 @@ def test_balance_at_cost_empty(): balance = related.balance_at_cost() assert balance.is_zero() +@pytest.mark.parametrize('date,expected', [ + (testutil.FY_MID_DATE - datetime.timedelta(days=1), 0), + (testutil.FY_MID_DATE, 0), + (testutil.FY_MID_DATE + datetime.timedelta(days=1), 25), + (testutil.FY_MID_DATE + datetime.timedelta(days=2), 70), + (testutil.FY_MID_DATE + datetime.timedelta(days=3), 135), + (testutil.FY_MID_DATE + datetime.timedelta(days=4), 135), +]) +def test_balance_at_cost_by_date(date, expected): + dates = testutil.date_seq() + jpy_cost = ('0.01', 'USD') + entries = [ + testutil.Transaction(date=next(dates), postings=[ + ('Assets:Cash', 1000, 'JPY', jpy_cost), + ('Assets:Cash', 15), + ]), + testutil.Transaction(date=next(dates), postings=[ + ('Assets:Cash', 2000, 'JPY', jpy_cost), + ('Assets:Cash', 25), + ]), + testutil.Transaction(date=next(dates), postings=[ + ('Assets:Cash', 3000, 'JPY', jpy_cost), + ('Assets:Cash', 35), + ]), + ] + related = core.RelatedPostings(data.Posting.from_entries(entries)) + actual = related.balance_at_cost_by_date(date) + if not expected: + assert actual.is_zero() + else: + assert actual == {'USD': testutil.Amount(expected)} + def test_meta_values_empty(): related = core.RelatedPostings() assert related.meta_values('key') == set()