Skip to content

Commit

Permalink
Add price to cost when needed by ledger
Browse files Browse the repository at this point in the history
Ledger doesn't use the cost to determine an exchange (see
ledger/ledger#630), which means
we have to add the price explicitly if ledger can't do
an implicit conversion.

This improves the logic that was added in commit e915d2b
("Support entries mixed with held-at-cost and price-conversion
postings") to deal with one test case.

Fixes #22
  • Loading branch information
tbm committed Nov 13, 2020
1 parent 4471bcc commit d02e876
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 12 deletions.
31 changes: 31 additions & 0 deletions beancount2ledger/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,34 @@ def get_lineno(posting):

meta = posting.meta or {}
return meta.get("lineno", sys.maxsize)


def is_automatic_posting(posting):
"""
Is posting an automatic posting added by beancount?
"""

if not posting.meta:
return False
if '__automatic__' in posting.meta and not '__residual__' in posting.meta:
return True
return False


def filter_display_postings(entry, dformat):
"""
Return entry without postings that wouldn't be displayed because
the display precision rounds them to 0.00.
"""

postings = list(entry.postings)
new_postings = []
for posting in postings:
pos_str = posting.units.to_string(dformat)
# Don't create a posting if the amount (rounded to the display
# precision) is 0.00.
amt = amount.from_string(pos_str)
if amt:
new_postings.append(posting)
entry = entry._replace(postings=new_postings)
return entry
25 changes: 13 additions & 12 deletions beancount2ledger/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from .common import ROUNDING_ACCOUNT
from .common import ledger_flag, ledger_str, quote_currency, postings_by_type, user_meta
from .common import set_default, get_lineno
from .common import set_default, get_lineno, is_automatic_posting, filter_display_postings


class LedgerPrinter:
Expand Down Expand Up @@ -82,6 +82,9 @@ def Transaction(self, entry):
# a bug, so instead, we simply insert a rounding account to absorb the
# residual and precisely balance the transaction.
entry = interpolate.fill_residual_posting(entry, ROUNDING_ACCOUNT)
# Remove postings which wouldn't be displayed (due to precision
# rounding amounts to 0.00)
entry = filter_display_postings(entry, self.dformat)

# Compute the string for the payee and narration line.
strings = []
Expand Down Expand Up @@ -150,14 +153,9 @@ def Posting(self, posting, entry):
# We don't use position.to_string() because that uses the same
# dformat for amount and cost, but we want dformat from our
# dcontext to format amounts to the right precision while
# retaining the full rpecision for costs.
# retaining the full precision for costs.
if isinstance(posting.units, Amount):
pos_str = posting.units.to_string(self.dformat)
# Don't create a posting if the amount (rounded to the display
# precision) is 0.00.
amt = amount.from_string(pos_str)
if not amt:
return
# We can't use default=True, even though we're interested in the
# cost details, but we have to add them ourselves in the format
# expected by ledger.
Expand All @@ -176,17 +174,20 @@ def Posting(self, posting, entry):
else:
# Figure out if we need to insert a price on a posting held at cost.
# See https://groups.google.com/d/msg/ledger-cli/35hA0Dvhom0/WX8gY_5kHy0J
(postings_simple, postings_at_price,
postings_at_cost) = postings_by_type(entry)

# and https://github.com/ledger/ledger/issues/630
(postings_simple, _, __) = postings_by_type(entry)
postings_no_amount = [
posting for posting in postings_simple
if not posting.units or is_automatic_posting(posting)
]
cost = posting.cost
if postings_at_price and postings_at_cost and cost:
if cost and not postings_no_amount and len(entry.postings) > 2:
price_str = '@ {}'.format(
amount.Amount(cost.number, cost.currency).to_string())
else:
price_str = ''

if posting.meta and '__automatic__' in posting.meta and not '__residual__' in posting.meta:
if is_automatic_posting(posting):
posting_str = f'{flag_posting}'
else:
# Width we have available for the amount: take width of
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Add rounding postings only when required ([issue #9](https://github.com/beancount/beancount2ledger/issues/9))
* Avoid printing too much precision for a currency ([issue #21](https://github.com/beancount/beancount2ledger/issues/21))
* Avoid creating two or more postings with null amount ([issue #23](https://github.com/beancount/beancount2ledger/issues/23))
* Add price to cost when needed by ledger ([issue #22](https://github.com/beancount/beancount2ledger/issues/22))
* Add config option `indent`
* Show metadata with hledger output
* Support setting auxiliary dates and posting dates from metadata ([issue #14](https://github.com/beancount/beancount2ledger/issues/14))
Expand Down
23 changes: 23 additions & 0 deletions tests/hledger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,29 @@ def test_avoid_multiple_null_postings(self, entries, _, ___):
Equity:Opening-balance
""", result)

@loader.load_doc()
def test_add_price_when_needed(self, entries, _, ___):
"""
2020-01-01 open Assets:Property
2020-01-01 open Equity:Opening-Balance
2020-11-13 * "Cost without price"
Assets:Property 0.1 FOO {300.00 EUR}
Assets:Property 0.2 BAR {200.00 EUR}
Equity:Opening-Balance -70.00 EUR
"""
result = beancount2ledger.convert(entries, "hledger")
self.assertLines(r"""
account Assets:Property
account Equity:Opening-Balance
2020-11-13 * Cost without price
Assets:Property 0.1 FOO @ 300.00 EUR
Assets:Property 0.2 BAR @ 200.00 EUR
Equity:Opening-Balance -70.00 EUR
""", result)

def test_example(self):
"""
Test converted example with hledger
Expand Down
23 changes: 23 additions & 0 deletions tests/ledger_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,29 @@ def test_avoid_multiple_null_postings(self, entries, _, ___):
Equity:Opening-balance
""", result)

@loader.load_doc()
def test_add_price_when_needed(self, entries, _, ___):
"""
2020-01-01 open Assets:Property
2020-01-01 open Equity:Opening-Balance
2020-11-13 * "We need to add @ price due to ledger bug #630"
Assets:Property 0.1 FOO {300.00 EUR}
Assets:Property 0.2 BAR {200.00 EUR}
Equity:Opening-Balance -70.00 EUR
"""
result = beancount2ledger.convert(entries)
self.assertLines(r"""
account Assets:Property
account Equity:Opening-Balance
2020-11-13 * We need to add @ price due to ledger bug #630
Assets:Property 0.1 FOO {300.00 EUR} @ 300.00 EUR
Assets:Property 0.2 BAR {200.00 EUR} @ 200.00 EUR
Equity:Opening-Balance -70.00 EUR
""", result)

def test_example(self):
with tempfile.NamedTemporaryFile('w',
suffix='.beancount',
Expand Down

0 comments on commit d02e876

Please # to comment.