Ejemplo n.º 1
0
    def test_iter_children(self):
        ra0 = RealAccount('')
        for account_name in ['Assets:US:Bank:Checking',
                             'Assets:US:Bank:Savings',
                             'Assets:US:Cash',
                             'Assets:CA:Cash']:
            realization.get_or_create(ra0, account_name)

        # Test enumerating all accounts.
        self.assertEqual(['',
                          'Assets',
                          'Assets:CA',
                          'Assets:CA:Cash',
                          'Assets:US',
                          'Assets:US:Bank',
                          'Assets:US:Bank:Checking',
                          'Assets:US:Bank:Savings',
                          'Assets:US:Cash'],
                         [ra.account for ra in realization.iter_children(ra0)])

        # Test enumerating leaves only.
        self.assertEqual(['Assets:CA:Cash',
                          'Assets:US:Bank:Checking',
                          'Assets:US:Bank:Savings',
                          'Assets:US:Cash'],
                         [ra.account for ra in realization.iter_children(ra0, True)])
Ejemplo n.º 2
0
def render_mini_balances(entries, options_map, conversion=None, price_map=None):
    """Render a treeified list of the balances for the given transactions.

    Args:
      entries: A list of selected transactions to render.
      options_map: The parsed options.
      conversion: Conversion method string, None, 'value' or 'cost'.
      price_map: A price map from the original entries. If this isn't provided,
        the inventories are rendered directly. If it is, their contents are
        converted to market value.
    """
    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry)
              for entry in entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(entries)
    dformat = options_map['dcontext'].build(alignment=Align.DOT, reserved=2)

    # TODO(blais): I always want to be able to convert at cost. We need
    # arguments capability.
    #
    # TODO(blais): Ideally this conversion inserts a new transactions to
    # 'Unrealized' to account for the difference between cost and market value.
    # Insert one and update the realization. Add an update() method to the
    # realization, given a transaction.
    acctypes = options.get_account_types(options_map)
    if conversion == 'value':
        assert price_map is not None

        # Warning: Mutate the inventories in-place, converting them to market
        # value.
        balance_diff = inventory.Inventory()
        for real_account in realization.iter_children(real_root):
            balance_cost = real_account.balance.reduce(convert.get_cost)
            balance_value = real_account.balance.reduce(convert.get_value, price_map)
            real_account.balance = balance_value
            balance_diff.add_inventory(balance_cost)
            balance_diff.add_inventory(-balance_value)
        if not balance_diff.is_empty():
            account_unrealized = account.join(acctypes.income,
                                              options_map["account_unrealized_gains"])
            unrealized = realization.get_or_create(real_root, account_unrealized)
            unrealized.balance.add_inventory(balance_diff)

    elif conversion == 'cost':
        for real_account in realization.iter_children(real_root):
            real_account.balance = real_account.balance.reduce(convert.get_cost)

    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account, acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Ejemplo n.º 3
0
    def _table_tree(self, real_accounts):
        """
        Renders real_accounts and it's children as a flat list to be used
        in rendering tables.

        Returns:
            [
                {
                    'account': 'Expenses:Vacation',
                    'balances_children': {
                        'USD': 123.45, ...
                    },
                    'balances': {
                        'USD': 123.45, ...
                    },
                    'is_leaf': True,
                    'postings_count': 3
                }, ...
            ]
        """

        # FIXME this does not seem correct
        if isinstance(real_accounts, None.__class__):
            return []

        lines = []
        for real_account in realization.iter_children(real_accounts):

            line = {
                'account': real_account.account,
                'balances_children': self._table_totals(real_account),
                'balances': {},
                'is_leaf': (len(list(realization.iter_children(real_account))) == 1), # True if the accoutn has no children or has entries
                'postings_count': len(real_account.txn_postings)
            }

            for pos in real_account.balance.cost():
                line['balances'][pos.lot.currency] = pos.number

            # Accounts that are not leafs but have entries are leafs as well
            for currency in self.options_map['commodities']:
                if currency in line['balances'] and currency in line['balances_children']:
                    if line['balances'][currency] != line['balances_children'][currency]:
                        line['is_leaf'] = True

            lines.append(line)

        return lines
Ejemplo n.º 4
0
def build_interesting_realacc(accapi, accounts):
    def is_included_account(realacc):
        for pattern in accounts:
            if re.match(pattern, realacc.account):
                if realacc.balance == inventory.Inventory():
                    return False  # Remove empty accounts to "clean up" the tree
                return True
        return False

    realroot = accapi.realize()

    # first, filter out accounts that are not specified:
    realacc = realization.filter(realroot, is_included_account)

    if not realacc:
        sys.stderr.write(
            "No included accounts found. (Your --accounts <regex> failed to match any account)\n"
        )
        sys.exit(1)

    # However, realacc includes all ancestor accounts of specified accounts, and their balances. For example,
    # if we specified 'Accounts:Investments:Brokerage', balances due to transactions on 'Accounts:Investments'
    # will also be included. We need to filter these out:
    for acc in realization.iter_children(realacc):
        if not is_included_account(acc):
            acc.balance = inventory.Inventory()
    return realacc
Ejemplo n.º 5
0
    def _table_totals(self, real_accounts):
        """
            Renders the total balances for root_acccounts and their children.

            Returns:
                {
                    'USD': 123.45,
                    ...
                }
        """

        totals = {}

        # FIXME This sometimes happens when called from self.account(...)
        #       and there is no entry in that specific month. This also produces
        #       a missing bar in the bar chart.
        if isinstance(real_accounts, None.__class__):
            return {}

        for real_account in realization.iter_children(real_accounts):
            for pos in real_account.balance.cost():
                if not pos.lot.currency in totals:
                    totals[pos.lot.currency] = ZERO
                totals[pos.lot.currency] += pos.number

        return totals
Ejemplo n.º 6
0
def validate_leaf_only(entries, unused_options_map):
    """Check for non-leaf accounts that have postings on them.

    Args:
      entries: A list of directives.
      unused_options_map: An options map.
    Returns:
      A list of new errors, if any were found.
    """
    real_root = realization.realize(entries, compute_balance=False)

    default_meta = data.new_metadata('<leafonly>', 0)
    open_close_map = None  # Lazily computed.
    errors = []
    for real_account in realization.iter_children(real_root):
        if len(real_account) > 0 and real_account.txn_postings:

            if open_close_map is None:
                open_close_map = getters.get_account_open_close(entries)

            open_entry = open_close_map[real_account.account][0]
            errors.append(
                LeafOnlyError(
                    open_entry.meta if open_entry else default_meta,
                    "Non-leaf account '{}' has postings on it".format(
                        real_account.account), open_entry))

    return entries, errors
Ejemplo n.º 7
0
    def _activity_by_account(self, account_name=None):
        nb_activity_by_account = []
        for real_account in realization.iter_children(self.root_account):
            if not isinstance(real_account, RealAccount):
                continue
            if account_name and real_account.account != account_name:
                continue

            last_posting = realization.find_last_active_posting(
                real_account.txn_postings)

            if last_posting is None or isinstance(last_posting, Close):
                continue

            entry = get_entry(last_posting)

            nb_activity_by_account.append({
                'account':
                real_account.account,
                'last_posting_date':
                entry.date,
                'last_posting_filename':
                entry.meta['filename'],
                'last_posting_lineno':
                entry.meta['lineno']
            })

        return nb_activity_by_account
Ejemplo n.º 8
0
def get_final_holdings(entries, included_account_types=None, price_map=None,
                       date=None):
    """Get a list of holdings by account (as Postings)."""

    simple_entries = [entry for entry in entries
                      if (not isinstance(entry, Transaction) or
                          entry.flag != flags.FLAG_UNREALIZED)]

    root_account = realization.realize(simple_entries)

    holdings = []

    for real_account in sorted(list(realization.iter_children(root_account)),
                               key=lambda ra: ra.account):
        account_type = account_types.get_account_type(real_account.account)
        if (included_account_types and
                account_type not in included_account_types):
            continue
        for pos in real_account.balance:
            price = None
            if pos.cost and price_map:
                base_quote = (pos.units.currency, pos.cost.currency)
                _, price = prices.get_price(price_map, base_quote, date)
            holdings.append(Posting(real_account.account,
                                    pos.units,
                                    pos.cost,
                                    price, None, None))

    return holdings
Ejemplo n.º 9
0
def _list_accounts(root_account, leaf_only=False):
    """List of all sub-accounts of the given root."""
    accounts = [child_account.account
                for child_account in
                realization.iter_children(root_account, leaf_only)]

    return accounts[1:]
Ejemplo n.º 10
0
    def _account_components(self, leaf_only=False):
        # TODO rename
        """Gather all the account components available in the given directives.

        Args:
          entries: A list of directive instances.
        Returns:
            [
                {
                    'name': 'TV',
                    'full_name': 'Expenses:Tech:TV',
                    'depth': 3
                }, ...
            ]
        """
        accounts = []
        for child_account in realization.iter_children(self.root_account,
                                                       leaf_only=leaf_only):
            accounts.append({
                'name': child_account.account.split(':')[-1],
                'full_name': child_account.account,
                'depth': child_account.account.count(':') + 1,
            })

        return accounts[1:]
Ejemplo n.º 11
0
    def _table_tree(self, real_accounts):
        """
        Renders real_accounts and it's children as a flat list to be used
        in rendering tables.

        Returns:
            [
                {
                    'account': 'Expenses:Vacation',
                    'balances_children': {
                        'USD': 123.45, ...
                    },
                    'balances': {
                        'USD': 123.45, ...
                    },
                    'is_leaf': True,
                    'postings_count': 3
                }, ...
            ]
        """

        lines = []
        for real_account in realization.iter_children(real_accounts):
            line = {
                'account': real_account.account,
                'balances_children': self._table_totals(real_account),
                'balances': self._inventory_to_json(real_account.balance, at_cost=True),
                'is_leaf': len(real_account) == 0 or real_account.txn_postings,
                'postings_count': len(real_account.txn_postings)
            }
            lines.append(line)

        return lines
Ejemplo n.º 12
0
def get_final_holdings(entries,
                       included_account_types=None,
                       price_map=None,
                       date=None):
    """Get a list of holdings by account (as Postings)."""

    simple_entries = [
        entry for entry in entries if (not isinstance(entry, Transaction)
                                       or entry.flag != flags.FLAG_UNREALIZED)
    ]

    root_account = realization.realize(simple_entries)

    holdings = []

    for real_account in sorted(list(realization.iter_children(root_account)),
                               key=lambda ra: ra.account):
        account_type = account_types.get_account_type(real_account.account)
        if (included_account_types
                and account_type not in included_account_types):
            continue
        for pos in real_account.balance:
            price = None
            if pos.cost and price_map:
                base_quote = (pos.units.currency, pos.cost.currency)
                _, price = prices.get_price(price_map, base_quote, date)
            holdings.append(
                Posting(real_account.account, pos.units, pos.cost, price, None,
                        None))

    return holdings
Ejemplo n.º 13
0
def do_linked(filename, args):
    """Print out a list of transactions linked to the one at the given line.

    Args:
      filename: A string, which consists in the filename.
      args: A tuple of the rest of arguments. We're expecting the first argument
        to be an integer as a string.
    """
    from beancount.parser import options
    from beancount.parser import printer
    from beancount.core import account_types
    from beancount.core import inventory
    from beancount.core import data
    from beancount.core import realization
    from beancount import loader

    # Parse the arguments, get the line number.
    if len(args) != 1:
        raise SystemExit("Missing line number argument.")
    lineno = int(args[0])

    # Load the input file.
    entries, errors, options_map = loader.load_file(filename)

    # Find the closest entry.
    closest_entry = data.find_closest(entries, options_map['filename'], lineno)

    # Find its links.
    links = closest_entry.links
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))
    if not links:
        return

    # Find all linked entries.
    linked_entries = [
        entry for entry in entries
        if (isinstance(entry, data.Transaction) and entry.links and entry.links
            & links)
    ]

    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in linked_entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(linked_entries)
    realization.dump_balances(real_root, file=sys.stdout)

    # Print out net income change.
    acctypes = options.get_account_types(options_map)
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Ejemplo n.º 14
0
def _list_accounts(root_account, active_only=False):
    """List of all sub-accounts of the given root."""
    accounts = [child_account.account
                for child_account in
                realization.iter_children(root_account)
                if not active_only or child_account.txn_postings]

    return accounts if active_only else accounts[1:]
Ejemplo n.º 15
0
def _list_accounts(root_account, active_only=False):
    """List of all sub-accounts of the given root."""
    accounts = [child_account.account
                for child_account in
                realization.iter_children(root_account)
                if not active_only or child_account.txn_postings]

    return accounts if active_only else accounts[1:]
Ejemplo n.º 16
0
    def _all_accounts(self, leaf_only=False):
        """Detailed list of all accounts."""
        accounts = [child_account.account
                    for child_account in
                    realization.iter_children(self.all_root_account,
                                              leaf_only=leaf_only)]

        return accounts[1:]
def tax_adjust(realacc):
    account_open_close = getters.get_account_open_close(entries)
    for acc in realization.iter_children(realacc):
        if acc.account in account_open_close:
            tax_adj = account_open_close[acc.account][0].meta.get(
                'asset_allocation_tax_adjustment', 100)
            acc.balance = scale_inventory(acc.balance, tax_adj)
    return realacc
Ejemplo n.º 18
0
    def _all_accounts(self, leaf_only=False):
        """Detailed list of all accounts."""
        accounts = [child_account.account
                    for child_account in
                    realization.iter_children(self.all_root_account,
                                              leaf_only=leaf_only)]

        return accounts[1:]
Ejemplo n.º 19
0
def _list_accounts(root_account, leaf_only=False):
    """List of all sub-accounts of the given root."""
    accounts = [
        child_account.account
        for child_account in realization.iter_children(root_account, leaf_only)
    ]

    return accounts[1:]
Ejemplo n.º 20
0
    def list_accounts(self, active_only=False):
        """List all sub-accounts of the root account."""
        accounts = [
            child_account.account for child_account in
            realization.iter_children(self.ledger.all_root_account)
            if not active_only or child_account.txn_postings
        ]

        return accounts if active_only else accounts[1:]
Ejemplo n.º 21
0
def test_tree_from_entries(example_ledger):
    tree = Tree(example_ledger.entries)
    real_account = realization.realize(example_ledger.entries)

    for account in realization.iter_children(real_account):
        name = account.account
        node = tree[name]
        _compare_inv_and_counter(account.balance, node.balance)
        _compare_inv_and_counter(realization.compute_balance(account),
                                 node.balance_children)
Ejemplo n.º 22
0
def main():
    logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s')
    parser = argparse.ArgumentParser(description=__doc__.strip())
    parser.add_argument('filename', help='Beancount input filename')
    args = parser.parse_args()

    entries, errors, options_map = loader.load_file(args.filename)

    root_account = realization.realize(entries)
    balances, _ = summarize.balance_by_account(entries, compress_unbooked=True)

    realization_account_names = {
        real_account.account
        for real_account in realization.iter_children(root_account)
        if not real_account.balance.is_empty()}

    summarize_account_names = {
        account
        for account, balance in balances.items()
        if not balance.is_empty()}

    if (realization_account_names - summarize_account_names):
        pprint.pprint(realization_account_names - summarize_account_names)
    if (summarize_account_names - realization_account_names):
        pprint.pprint(summarize_account_names - realization_account_names)

    for real_account in sorted(list(realization.iter_children(root_account)),
                               key=lambda ra: ra.account):
        summarize_balance = balances.get(real_account.account, inventory.Inventory())

        if summarize_balance == real_account.balance:
            continue

        print(real_account.account)
        print("    realization")
        for pos in real_account.balance:
            print("      {}".format(pos))
        print("    summarization")
        for pos in summarize_balance:
            print("      {}".format(pos))
        print()
Ejemplo n.º 23
0
 def test_filter_almost_all(self):
     # Test filtering that culls leaves, to make sure that works.
     real_root = create_real([('Assets:US:Bank:Checking', '1 USD'),
                              ('Assets:US:Bank:Savings', '2 USD'),
                              ('Liabilities:USBank:CreditCard', '3 USD'),
                              ('Assets', '100 USD'),
                              ('Liabilities:US:Bank', '101 USD')])
     def ge100(ra0):
         return ra0.balance.get_currency_units('USD').number >= 100
     real_copy = realization.filter(real_root, ge100)
     self.assertTrue(real_copy is not None)
     self.assertEqual({'Assets', 'Liabilities:US:Bank'},
                      set(ra0.account
                          for ra0 in realization.iter_children(real_copy, True)))
Ejemplo n.º 24
0
 def _table_tree(self, real_account):
     """
     Renders real_account and it's children as a flat list to be used
     in rendering tables.
     """
     return [{
         'account': ra.account,
         'balances_children':
             serialize_inventory(realization.compute_balance(ra),
                                 at_cost=True),
         'balances': serialize_inventory(ra.balance, at_cost=True),
         'is_leaf': len(ra) == 0 or bool(ra.txn_postings),
         'postings_count': len(ra.txn_postings)
     } for ra in realization.iter_children(real_account)]
Ejemplo n.º 25
0
 def test_filter_no_leaves(self):
     # Test filtering that drops leaf nodes but that keeps intermediate
     # nodes.
     real_root = create_real([('Assets:US:Bank:Checking', '1 USD'),
                              ('Assets:US:Bank:Savings', '2 USD'),
                              ('Assets:US', '100 USD'),
                              ('Assets', '100 USD')])
     def ge100(ra0):
         return ra0.balance.get_currency_units('USD').number >= 100
     real_copy = realization.filter(real_root, ge100)
     self.assertTrue(real_copy is not None)
     self.assertEqual({'Assets:US'},
                      set(ra0.account
                          for ra0 in realization.iter_children(real_copy, True)))
Ejemplo n.º 26
0
    def test_simple_realize(self, entries, errors, options_map):
        """
          2013-05-01 open Assets:US:Checking:Sub   USD
          2013-05-01 open Expenses:Stuff
          2013-05-02 txn "Testing!"
            Assets:US:Checking:Sub            100 USD
            Expenses:Stuff           -100 USD
        """
        real_root = realization.realize(entries)
        for real_account in realization.iter_children(real_root):
            assert isinstance(real_account, realization.RealAccount)

        for account_name in ['Assets:US:Checking:Sub', 'Expenses:Stuff']:
            real_account = realization.get(real_root, account_name)
            self.assertEqual(account_name, real_account.account)
Ejemplo n.º 27
0
 def test_filter_with_leaves(self):
     # Test filtering that keeps some leaf nodes with some intermediate nodes
     # that would otherwise be eliminated.
     real_root = create_real([('Assets:US:Bank:Checking', '1 USD'),
                              ('Assets:US:Bank:Savings', '2 USD'),
                              ('Liabilities:USBank:CreditCard', '3 USD')])
     def not_empty(ra0):
         return not ra0.balance.is_empty()
     real_copy = realization.filter(real_root, not_empty)
     self.assertTrue(real_copy is not None)
     self.assertEqual({'Assets:US:Bank:Checking',
                       'Assets:US:Bank:Savings',
                       'Liabilities:USBank:CreditCard'},
                      set(ra0.account
                          for ra0 in realization.iter_children(real_copy, True)))
Ejemplo n.º 28
0
 def test_filter_misc(self):
     real_root = create_real([('Assets:US:Bank:Checking', '1 USD'),
                              ('Assets:US:Bank:Savings', '2 USD'),
                              ('Assets:US:Cash', '3 USD'),
                              ('Assets:CA:Cash', '4 USD'),
                              ('Liabilities:Bank:CreditCard', '5 USD'),
                              ('Expenses:Food:Grocery', '6 USD'),
                              ('Expenses:Food:Restaurant', '7 USD'),
                              ('Expenses:Food:Alcohol', '8 USD'),
                              ('Expenses:Food', '10 USD')])
     def even(real_account):
         return (not real_account.balance.is_empty() and
                 real_account.balance.get_currency_units('NOK').number % 2 == 0)
     real_even = realization.filter(real_root, even)
     self.assertTrue(all(map(even, realization.iter_children(real_even, True))))
Ejemplo n.º 29
0
def test_tree_cap(example_ledger):
    closing_entries = summarize.cap_opt(example_ledger.entries,
                                        example_ledger.options)
    real_account = realization.realize(closing_entries)

    tree = Tree(example_ledger.entries)
    tree.cap(example_ledger.options, 'Unrealized')

    for account in realization.iter_children(real_account):
        name = account.account
        node = tree[name]
        if not name:
            continue
        if name.startswith('Expenses') or name.startswith('Income'):
            continue
        _compare_inv_and_counter(account.balance, node.balance)
Ejemplo n.º 30
0
def tax_adjust(realacc, accapi):
    def scale_inventory(balance, tax_adj):
        """Scale inventory by tax adjustment"""
        scaled_balance = inventory.Inventory()
        for pos in balance.get_positions():
            scaled_pos = amount.Amount(
                pos.units.number * (Decimal(tax_adj / 100)),
                pos.units.currency)
            scaled_balance.add_amount(scaled_pos)
        return scaled_balance

    account_open_close = accapi.get_account_open_close()
    for acc in realization.iter_children(realacc):
        if acc.account in account_open_close:
            tax_adj = account_open_close[acc.account][0].meta.get(
                'asset_allocation_tax_adjustment', 100)
            acc.balance = scale_inventory(acc.balance, tax_adj)
    return realacc
Ejemplo n.º 31
0
    def _table_tree(self, real_accounts):
        """
        Renders real_accounts and it's children as a flat list to be used
        in rendering tables.

        Returns:
            [
                {
                    'account': 'Expenses:Vacation',
                    'balances_children': {
                        'USD': 123.45, ...
                    },
                    'balances': {
                        'USD': 123.45, ...
                    },
                    'is_leaf': True,
                    'postings_count': 3
                }, ...
            ]
        """

        lines = []
        for real_account in realization.iter_children(real_accounts):
            line = {
                'account':
                real_account.account,
                'balances_children':
                self._table_totals(real_account),
                'balances':
                self._inventory_to_json(real_account.balance, at_cost=True),
                'is_leaf':
                len(real_account) == 0 or real_account.txn_postings,
                'postings_count':
                len(real_account.txn_postings)
            }
            lines.append(line)

        return lines
Ejemplo n.º 32
0
    def _activity_by_account(self, account_name=None):
        nb_activity_by_account = []
        for real_account in realization.iter_children(self.root_account):
            if not isinstance(real_account, RealAccount):
                continue
            if account_name and real_account.account != account_name:
                continue

            last_posting = realization.find_last_active_posting(
                real_account.txn_postings)

            if last_posting is None or isinstance(last_posting, Close):
                continue

            entry = get_entry(last_posting)

            nb_activity_by_account.append({
                'account': real_account.account,
                'last_posting_date': entry.date,
                'last_posting_filename': entry.meta['filename'],
                'last_posting_lineno': entry.meta['lineno']
            })

        return nb_activity_by_account
Ejemplo n.º 33
0
    def _account_components(self, leaf_only=False):
        # TODO rename
        """Gather all the account components available in the given directives.

        Args:
          entries: A list of directive instances.
        Returns:
            [
                {
                    'name': 'TV',
                    'full_name': 'Expenses:Tech:TV',
                    'depth': 3
                }, ...
            ]
        """
        accounts = []
        for child_account in realization.iter_children(self.root_account, leaf_only=leaf_only):
            accounts.append({
                'name': child_account.account.split(':')[-1],
                'full_name': child_account.account,
                'depth': child_account.account.count(':')+1,
            })

        return accounts[1:]
Ejemplo n.º 34
0
def read_assets(filename, currency, reduce_accounts, quantization):
    """Read a Beancount file and produce a list of assets.

    Args:
      filename: A string, the path to the Beancount file to read.
      currency: A string, the currency to convert all the contents to.
      reduce_accounts: A set of account names to be aggregated.
      quantization: A Decimal instance, to quantize all the resulting amounts.
    Returns:
      A list of (account-name, number-balance), numbers being assumed to be in
      the requested currency.
    """

    # Read the Beancount input file.
    entries, _, options_map = loader.load_file(filename,
                                               log_errors=logging.error)
    acctypes = options.get_account_types(options_map)
    price_map = prices.build_price_map(entries)
    ocmap = getters.get_account_open_close(entries)

    # Compute aggregations.
    real_root = realization.realize(entries, compute_balance=True)

    # Reduce accounts which have been found in details (mutate the tree in-place).
    for account in reduce_accounts:
        real_acc = realization.get(real_root, account)
        real_acc.balance = realization.compute_balance(real_acc)
        real_acc.clear()

    # Prune all the closed accounts and their parents.
    real_root = prune_closed_accounts(real_root, ocmap)

    # Produce a list of accounts and their balances reduced to a single currency.
    acceptable_types = (acctypes.assets, acctypes.liabilities)
    accounts = []
    for real_acc in realization.iter_children(real_root):
        atype = account_types.get_account_type(real_acc.account)
        if atype not in acceptable_types:
            continue

        try:
            _, close = ocmap[real_acc.account]
            if close is not None:
                continue
        except KeyError:
            #logging.info("Account not there: {}".format(real_acc.account))
            if real_acc.account not in reduce_accounts and real_acc.balance.is_empty(
            ):
                continue

        value_inv = real_acc.balance.reduce(
            lambda x: convert.get_value(x, price_map))
        currency_inv = value_inv.reduce(convert.convert_position, currency,
                                        price_map)
        amount = currency_inv.get_currency_units(currency)
        accounts.append(
            (real_acc.account, amount.number.quantize(quantization)))

    # Reduce this list of (account-name, balance-number) sorted by reverse amount order.
    accounts.sort(key=lambda x: x[1], reverse=True)
    return accounts
Ejemplo n.º 35
0
def table_of_balances(real_root,
                      operating_currencies,
                      formatter,
                      classes=None):
    """Render a tree table with the balance of each accounts.

    Args:
      real_root: A RealAccount node, the root node to render.
      operating_currencies: A list of strings, the operating currencies to render
        to their own dedicated columns.
      formatter: A object used to render account names and other links.
      classes: A list of strings, the CSS classes to attach to the renderd
        top-level table objet.
    Returns:
      A string with HTML contents, the rendered table.
    """
    header = ['Account'] + operating_currencies + ['Other']

    # Pre-calculate which accounts should be rendered.
    real_active = realization.filter(real_root, is_account_active)
    if real_active:
        active_set = {
            real_account.account
            for real_account in realization.iter_children(real_active)
        }
    else:
        active_set = set()

    balance_totals = inventory.Inventory()
    oss = io.StringIO()
    classes = list(classes) if classes else []
    classes.append('fullwidth')
    for real_account, cells, row_classes in tree_table(oss, real_root,
                                                       formatter, header,
                                                       classes):

        if real_account is TOTALS_LINE:
            line_balance = balance_totals
            row_classes.append('totals')
        else:
            # Check if this account has had activity; if not, skip rendering it.
            if (real_account.account not in active_set and
                    not account_types.is_root_account(real_account.account)):
                continue

            if real_account.account is None:
                row_classes.append('parent-node')

            # For each account line, get the final balance of the account (at cost).
            line_balance = real_account.balance.reduce(convert.get_cost)

            # Update the total balance for the totals line.
            balance_totals += line_balance

        # Extract all the positions that the user has identified as operating
        # currencies to their own subinventories.
        ccy_dict = line_balance.segregate_units(operating_currencies)

        # FIXME: This little algorithm is inefficient; rewrite it.
        for currency in operating_currencies:
            units = ccy_dict[currency].get_currency_units(currency)
            cells.append(
                formatter.render_number(units.number, units.currency
                                        ) if units.number != ZERO else '')

        # Render all the rest of the inventory in the last cell.
        if None in ccy_dict:
            ccy_balance = ccy_dict[None]
            last_cell = '<br/>'.join(
                formatter.render_amount(pos.units)
                for pos in sorted(ccy_balance))
        else:
            last_cell = ''
        cells.append(last_cell)

    return oss.getvalue()
Ejemplo n.º 36
0
def get_final_holdings(entries,
                       included_account_types=None,
                       price_map=None,
                       date=None):
    """Get a dictionary of the latest holdings by account.

    This basically just flattens the balance sheet's final positions, including
    that of equity accounts. If a 'price_map' is provided, insert price
    information in the flattened holdings at the latest date, or at the given
    date, if one is provided.

    Only the accounts in 'included_account_types' will be included, and this is
    always called for Assets and Liabilities only. If left unspecified, holdings
    from all account types will be included, including Equity, Income and
    Expenses.

    Args:
      entries: A list of directives.
      included_account_types: A sequence of strings, the account types to
        include in the output. A reasonable example would be
        ('Assets', 'Liabilities'). If not specified, include all account types.
      price_map: A dict of prices, as built by prices.build_price_map().
      date: A datetime.date instance, the date at which to price the
        holdings. If left unspecified, we use the latest price information.
    Returns:
      A list of dicts, with the following fields:
    """
    # Remove the entries inserted by unrealized gains/losses. Those entries do
    # affect asset accounts, and we don't want them to appear in holdings.
    #
    # Note: Perhaps it would make sense to generalize this concept of "inserted
    # unrealized gains."
    simple_entries = [
        entry for entry in entries if (not isinstance(entry, data.Transaction)
                                       or entry.flag != flags.FLAG_UNREALIZED)
    ]

    # Realize the accounts into a tree (because we want the positions by-account).
    root_account = realization.realize(simple_entries)

    # For each account, look at the list of positions and build a list.
    holdings = []
    for real_account in sorted(list(realization.iter_children(root_account)),
                               key=lambda ra: ra.account):

        if included_account_types:
            # Skip accounts of invalid types, we only want to reflect the requested
            # account types, typically assets and liabilities.
            account_type = account_types.get_account_type(real_account.account)
            if account_type not in included_account_types:
                continue

        for pos in real_account.balance.get_positions():
            if pos.cost is not None:
                # Get price information if we have a price_map.
                market_value = None
                if price_map is not None:
                    base_quote = (pos.units.currency, pos.cost.currency)
                    price_date, price_number = prices.get_price(
                        price_map, base_quote, date)
                    if price_number is not None:
                        market_value = pos.units.number * price_number
                else:
                    price_date, price_number = None, None

                holding = Holding(real_account.account, pos.units.number,
                                  pos.units.currency, pos.cost.number,
                                  pos.cost.currency,
                                  pos.units.number * pos.cost.number,
                                  market_value, price_number, price_date)
            else:
                holding = Holding(real_account.account, pos.units.number,
                                  pos.units.currency, None, pos.units.currency,
                                  pos.units.number, pos.units.number, None,
                                  None)
            holdings.append(holding)

    return holdings
Ejemplo n.º 37
0
def do_linked(filename, args):
    """Print out a list of transactions linked to the one at the given line.

    Args:
      filename: A string, which consists in the filename.
      args: A tuple of the rest of arguments. We're expecting the first argument
        to be an integer as a string.
    """
    from beancount.parser import options
    from beancount.parser import printer
    from beancount.core import account_types
    from beancount.core import inventory
    from beancount.core import data
    from beancount.core import realization
    from beancount import loader

    # Parse the arguments, get the line number.
    if len(args) != 1:
        raise SystemExit("Missing line number argument.")
    lineno = int(args[0])

    # Load the input file.
    entries, errors, options_map = loader.load_file(filename)

    # Find the closest entry.
    closest_entry = data.find_closest(entries, options_map['filename'], lineno)

    # Find its links.
    if closest_entry is None:
        raise SystemExit("No entry could be found before {}:{}".format(
            filename, lineno))
    links = (closest_entry.links if isinstance(closest_entry, data.Transaction)
             else data.EMPTY_SET)
    if not links:
        linked_entries = [closest_entry]
    else:
        # Find all linked entries.
        #
        # Note that there is an option here: You can either just look at the links
        # on the closest entry, or you can include the links of the linked
        # transactions as well. Whichever one you want depends on how you use your
        # links. Best would be to query the user (in Emacs) when there are many
        # links present.
        follow_links = True
        if not follow_links:
            linked_entries = [
                entry for entry in entries
                if (isinstance(entry, data.Transaction) and entry.links
                    and entry.links & links)
            ]
        else:
            links = set(links)
            linked_entries = []
            while True:
                num_linked = len(linked_entries)
                linked_entries = [
                    entry for entry in entries
                    if (isinstance(entry, data.Transaction) and entry.links
                        and entry.links & links)
                ]
                if len(linked_entries) == num_linked:
                    break
                for entry in linked_entries:
                    if entry.links:
                        links.update(entry.links)

    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in linked_entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(linked_entries)
    dformat = options_map['dcontext'].build(
        alignment=display_context.Align.DOT, reserved=2)
    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    acctypes = options.get_account_types(options_map)
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Ejemplo n.º 38
0
def do_linked(filename, args):
    """Print out a list of transactions linked to the one at the given line.

    Args:
      filename: A string, which consists in the filename.
      args: A tuple of the rest of arguments. We're expecting the first argument
        to be a string which contains either a lineno integer or a filename:lineno
        combination (which can be used if the location is not in the top-level file).
    """
    from beancount.parser import options
    from beancount.parser import printer
    from beancount.core import account_types
    from beancount.core import inventory
    from beancount.core import data
    from beancount.core import realization
    from beancount import loader

    # Parse the arguments, get the line number.
    if len(args) != 1:
        raise SystemExit("Missing line number or link argument.")
    location_spec = args[0]

    # Load the input file.
    entries, errors, options_map = loader.load_file(filename)

    # Accept an explicit link name as the location. Must include the '^'
    # character.
    if re.match(r"\^(.*)$", location_spec):
        search_filename = options_map['filename']
        links = {location_spec[1:]}
        linked_entries = find_linked_entries(entries, links, False)

    else:
        # Parse the argument as a line number or a "<filename>:<lineno>" spec to
        # pull context from.
        match = re.match(r"(.+):(\d+)$", location_spec)
        if match:
            search_filename = path.abspath(match.group(1))
            lineno = int(match.group(2))
        elif re.match(r"(\d+)$", location_spec):
            # Parse the argument as just a line number to pull context from on
            # the main filename.
            search_filename = options_map['filename']
            lineno = int(location_spec)
        else:
            raise SystemExit(
                "Invalid line number or link format for location.")

        # Find the closest entry.
        closest_entry = data.find_closest(entries, search_filename, lineno)

        # Find its links.
        if closest_entry is None:
            raise SystemExit("No entry could be found before {}:{}".format(
                search_filename, lineno))
        links = (closest_entry.links if isinstance(
            closest_entry, data.Transaction) else data.EMPTY_SET)

        # Get the linked entries, or just the closest one, if no links.
        linked_entries = (find_linked_entries(entries, links, True)
                          if links else [closest_entry])

    # Render linked entries (in date order) as errors (for Emacs).
    errors = [RenderError(entry.meta, '', entry) for entry in linked_entries]
    printer.print_errors(errors)

    # Print out balances.
    real_root = realization.realize(linked_entries)
    dformat = options_map['dcontext'].build(
        alignment=display_context.Align.DOT, reserved=2)
    realization.dump_balances(real_root, dformat, file=sys.stdout)

    # Print out net income change.
    acctypes = options.get_account_types(options_map)
    net_income = inventory.Inventory()
    for real_node in realization.iter_children(real_root):
        if account_types.is_income_statement_account(real_node.account,
                                                     acctypes):
            net_income.add_inventory(real_node.balance)

    print()
    print('Net Income: {}'.format(-net_income))
Ejemplo n.º 39
0
def check(entries, options_map):
    """Process the balance assertion directives.

    For each Balance directive, check that their expected balance corresponds to
    the actual balance computed at that time and replace failing ones by new
    ones with a flag that indicates failure.

    Args:
      entries: A list of directives.
      options_map: A dict of options, parsed from the input file.
    Returns:
      A pair of a list of directives and a list of balance check errors.
    """
    new_entries = []
    check_errors = []

    # This is similar to realization, but performed in a different order, and
    # where we only accumulate inventories for accounts that have balance
    # assertions in them (this saves on time). Here we process the entries one
    # by one along with the balance checks. We use a temporary realization in
    # order to hold the incremental tree of balances, so that we can easily get
    # the amounts of an account's subaccounts for making checks on parent
    # accounts.
    real_root = realization.RealAccount('')

    # Figure out the set of accounts for which we need to compute a running
    # inventory balance.
    asserted_accounts = {
        entry.account
        for entry in entries if isinstance(entry, Balance)
    }

    # Add all children accounts of an asserted account to be calculated as well,
    # and pre-create these accounts, and only those (we're just being tight to
    # make sure).
    asserted_match_list = [
        account.parent_matcher(account_) for account_ in asserted_accounts
    ]
    for account_ in getters.get_accounts(entries):
        if (account_ in asserted_accounts
                or any(match(account_) for match in asserted_match_list)):
            realization.get_or_create(real_root, account_)

    # Get the Open directives for each account.
    open_close_map = getters.get_account_open_close(entries)

    for entry in entries:
        if isinstance(entry, Transaction):
            # For each of the postings' accounts, update the balance inventory.
            for posting in entry.postings:
                real_account = realization.get(real_root, posting.account)

                # The account will have been created only if we're meant to track it.
                if real_account is not None:
                    # Note: Always allow negative lots for the purpose of balancing.
                    # This error should show up somewhere else than here.
                    real_account.balance.add_position(posting)

        elif isinstance(entry, Balance):
            # Check that the currency of the balance check is one of the allowed
            # currencies for that account.
            expected_amount = entry.amount
            try:
                open, _ = open_close_map[entry.account]
            except KeyError:
                check_errors.append(
                    BalanceError(
                        entry.meta,
                        "Account '{}' does not exist: ".format(entry.account),
                        entry))
                continue

            if (expected_amount is not None and open and open.currencies
                    and expected_amount.currency not in open.currencies):
                check_errors.append(
                    BalanceError(
                        entry.meta,
                        "Invalid currency '{}' for Balance directive: ".format(
                            expected_amount.currency), entry))

            # Check the balance against the check entry.
            real_account = realization.get(real_root, entry.account)
            assert real_account is not None, "Missing {}".format(entry.account)

            # Sum up the current balances for this account and its
            # sub-accounts. We want to support checks for parent accounts
            # for the total sum of their subaccounts.
            subtree_balance = inventory.Inventory()
            for real_child in realization.iter_children(real_account, False):
                subtree_balance += real_child.balance

            # Get only the amount in the desired currency.
            balance_amount = subtree_balance.get_currency_units(
                expected_amount.currency)

            # Check if the amount is within bounds of the expected amount.
            diff_amount = amount.sub(balance_amount, expected_amount)

            # Use the specified tolerance or automatically infer it.
            tolerance = get_balance_tolerance(entry, options_map)

            if abs(diff_amount.number) > tolerance:
                check_errors.append(
                    BalanceError(
                        entry.meta,
                        ("Balance failed for '{}': "
                         "expected {} != accumulated {} ({} {})").format(
                             entry.account, expected_amount, balance_amount,
                             abs(diff_amount.number),
                             ('too much' if diff_amount.number > 0 else
                              'too little')), entry))

                # Substitute the entry by a failing entry, with the diff_amount
                # field set on it. I'm not entirely sure that this is the best
                # of ideas, maybe leaving the original check intact and insert a
                # new error entry might be more functional or easier to
                # understand.
                entry = entry._replace(meta=entry.meta.copy(),
                                       diff_amount=diff_amount)

        new_entries.append(entry)

    return new_entries, check_errors