Exemplo n.º 1
0
    def test_fill_residual_posting(self, entries, _, __):
        """
        2001-01-01 open Assets:Account1
        2001-01-01 open Assets:Other

        2014-01-01 *
          Assets:Account1      100.00 USD
          Assets:Other        -100.00 USD

        2014-01-02 *
          Assets:Account1      100.00 USD
          Assets:Other        -100.00 USD

        2014-01-03 *
          Assets:Account1      100.00 USD
          Assets:Other        -100.0000001 USD

        2014-01-04 *
          Assets:Account1      100.00 USD
          Assets:Other        -112.69 CAD @ 0.8875 USD
        """
        account = 'Equity:Rounding'
        entries = [entry for entry in entries if isinstance(entry, data.Transaction)]
        for index in 0, 1:
            entry = interpolate.fill_residual_posting(entries[index], account)
            self.assertEqualEntries([entries[index]], [entry])
            residual = interpolate.compute_residual(entry.postings)
            self.assertTrue(residual.is_empty())

        entry = interpolate.fill_residual_posting(entries[2], account)
        self.assertEqualEntries("""

        2014-01-03 *
          Assets:Account1      100.00 USD
          Assets:Other        -100.0000001 USD
          Equity:Rounding        0.0000001 USD

        """, [entry])
        residual = interpolate.compute_residual(entry.postings)
        # Note: The residual calcualtion ignores postings inserted by the
        # rounding account.
        self.assertFalse(residual.is_empty())
        self.assertEqual(inventory.from_string('-0.0000001 USD'), residual)

        entry = interpolate.fill_residual_posting(entries[3], account)
        self.assertEqualEntries("""

        2014-01-04 *
          Assets:Account1     100.00 USD
          Assets:Other       -112.69 CAD @ 0.8875 USD
          Equity:Rounding   0.012375 USD

        """, [entry])
        residual = interpolate.compute_residual(entry.postings)
        # Same as above.
        self.assertFalse(residual.is_empty())
        self.assertEqual(inventory.from_string('-0.012375 USD'), residual)
Exemplo n.º 2
0
    def Transaction(self, entry, oss):
        strings = []

        # Insert a posting to absorb the residual if necessary. This is
        # sometimes needed because Ledger bases its balancing precision on the
        # *last* number of digits used on that currency. This is believed to be
        # 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)

        if entry.tags:
            for tag in sorted(entry.tags):
                strings.append(';; Tag: #{}'.format(tag))
        if entry.links:
            for link in sorted(entry.links):
                strings.append(';; Link: ^{}'.format(link))

        # Compute the string for the payee and narration line.
        if entry.payee:
            strings.append('{} |'.format(entry.payee))
        if entry.narration:
            strings.append(entry.narration)

        oss.write('{e.date:%Y/%m/%d} {flag} {}\n'.format(' '.join(strings),
                                                         flag=entry.flag or '',
                                                         e=entry))

        for posting in entry.postings:
            self.Posting(posting, entry, oss)
Exemplo n.º 3
0
    def Transaction(self, entry):
        # Insert a posting to absorb the residual if necessary. This is
        # sometimes needed because Ledger bases its balancing precision on the
        # *last* number of digits used on that currency. This is believed to be
        # 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_rounding_postings(entry, self.dformat)

        # Compute the string for the payee and narration line.
        strings = []
        if entry.payee:
            strings.append(f"{ledger_str(entry.payee)} |")
        if entry.narration:
            strings.append(ledger_str(entry.narration))

        meta = user_meta(entry.meta or {})
        self.io.write(f"{entry.date:%Y-%m-%d}")
        auxdate_key = self.config.get("auxdate")
        if auxdate_key and isinstance(meta.get(auxdate_key), datetime.date):
            self.io.write(f"={meta[auxdate_key]:%Y-%m-%d}")
            del meta[auxdate_key]
        flag = ledger_flag(entry.flag)
        if flag:
            self.io.write(" " + flag)
        code_key = self.config.get("code")
        if code_key and not meta.get(code_key) is None:
            code = meta[code_key]
            self.io.write(" (" + str(code) + ")")
            del meta[code_key]
        payee = " ".join(strings)
        if payee:
            self.io.write(" " + payee)
        self.io.write("\n")

        indent = " " * self.config["indent"]

        if entry.tags:
            self.io.write(indent +
                          "; {}:\n".format(":, ".join(sorted(entry.tags))))
        if entry.links:
            self.io.write(indent +
                          "; Link: {}\n".format(" ".join(sorted(entry.links))))

        for key, val in meta.items():
            meta = self.format_meta(key, val)
            if meta:
                self.io.write(indent + f"; {meta}\n")

        # If a posting without an amount is given and several amounts would
        # be added when balancing, beancount will create several postings.
        # But we ignore the amount on those postings (since they were added
        # by beancount and not the user), which means we may end up with
        # two or more postings with no amount, which is not valid.
        # Therefore, only take *one* posting by looking at the line number.
        seen = set()
        for posting in sorted(entry.postings, key=lambda p: get_lineno(p)):
            lineno = get_lineno(posting)
            if lineno is not None:
                if lineno in seen:
                    continue
                seen.add(lineno)
            self.Posting(posting, entry)