Exemple #1
0
    def monthly_income_expenses_totals(self):
        month_tuples = self._month_tuples(self.entries)
        monthly_totals = []
        for begin_date, end_date in month_tuples:
            entries, index = summarize.clamp_opt(self.entries, begin_date, end_date + timedelta(days=1),
                                                          self.options_map)

            income_totals = self._table_totals(realization.get(realization.realize(entries, self.account_types), self.options_map['name_income']))
            expenses_totals = self._table_totals(realization.get(realization.realize(entries, self.account_types), self.options_map['name_expenses']))

            # FIXME find better way to only include relevant totals (lots of ZERO-ones at the beginning)
            sum_ = ZERO
            for currency, number in income_totals.items():
                sum_ += number
            for currency, number in expenses_totals.items():
                sum_ += number

            if sum_ != ZERO:
                monthly_totals.append({
                    'begin_date': begin_date,
                    'end_date': end_date,
                    'income_totals': income_totals,
                    'expenses_totals': expenses_totals
                })

        return monthly_totals
Exemple #2
0
    def render_real_htmldiv(self, real_root, price_map, price_date,
                            options_map, file):
        # Render the income statement tables.
        operating_currencies = options_map['operating_currency']
        income = tree_table.table_of_balances(
            realization.get(real_root, options_map['name_income']), price_map,
            price_date, operating_currencies, self.formatter)
        expenses = tree_table.table_of_balances(
            realization.get(real_root, options_map['name_expenses']),
            price_map, price_date, operating_currencies, self.formatter)

        file.write("""
           <div id="income" class="halfleft">

             <div id="income">
              <h3>Income</h3>
              {income}
             </div>

           </div>
           <div class="halfright">

             <div id="expenses">
              <h3>Expenses</h3>
              {expenses}
             </div>

           </div>
        """.format(**locals()))
Exemple #3
0
    def monthly_income_expenses_totals(self):
        month_tuples = self._month_tuples(self.entries)
        monthly_totals = []
        for begin_date, end_date in month_tuples:
            entries = self._entries_in_inclusive_range(begin_date, end_date)
            realized = realization.realize(entries, self.account_types)
            income_totals = self._table_totals(realization.get(realized, self.account_types.income))
            expenses_totals = self._table_totals(realization.get(realized, self.account_types.expenses))

            # FIXME find better way to only include relevant totals (lots of ZERO-ones at the beginning)
            sum_ = ZERO
            for currency, number in income_totals.items():
                sum_ += number
            for currency, number in expenses_totals.items():
                sum_ += number

            if sum_ != ZERO:
                monthly_totals.append({
                    'begin_date': begin_date,
                    'end_date': end_date,
                    'income_totals': income_totals,
                    'expenses_totals': expenses_totals
                })

        return monthly_totals
Exemple #4
0
    def test_compare_realizations(self):
        # Check that value comparison uses our balance comparison properly.
        map1 = {'Assets:US:Bank:Checking': inventory.Inventory()}
        map2 = {'Assets:US:Bank:Checking': inventory.Inventory()}
        map2['Assets:US:Bank:Checking'].add_amount(A('0.01 USD'))
        self.assertNotEqual(map1, map2)

        # Now check this with accounts.
        root1 = RealAccount('')
        ra1 = realization.get_or_create(root1, 'Assets:US:Bank:Checking')
        ra1.balance.add_amount(A('0.01 USD'))
        root2 = RealAccount('')
        ra2 = realization.get_or_create(root2, 'Assets:US:Bank:Checking')
        ra2.balance.add_amount(A('0.01 USD'))
        self.assertEqual(ra1, ra2)

        root3 = copy.deepcopy(root2)
        ra3 = realization.get(root3, 'Assets:US:Bank:Checking')
        ra3.account = 'Liabilities:US:CreditCard'
        self.assertNotEqual(root1, root3)

        root3 = copy.deepcopy(root2)
        ra3 = realization.get(root3, 'Assets:US:Bank:Checking')
        ra3.balance.add_amount(A('0.01 CAD'))
        self.assertNotEqual(root1, root3)

        root3 = copy.deepcopy(root2)
        ra3 = realization.get(root3, 'Assets:US:Bank:Checking')
        ra3.txn_postings.append('posting')
        self.assertNotEqual(root1, root3)

        root3 = copy.deepcopy(root2)
        ra3 = realization.get(root3, 'Assets:US:Bank:Checking')
        ra3['Sub'] = RealAccount('Assets:US:Bank:Checking:Sub')
        self.assertNotEqual(root1, root3)
Exemple #5
0
    def render_real_htmldiv(self, real_root, options_map, file):
        account_types = options.get_account_types(options_map)
        for root in (account_types.assets, account_types.liabilities):
            oss = io.StringIO()
            table = tree_table.tree_table(oss,
                                          realization.get(real_root, root),
                                          None, ['Account', 'Last Entry'])
            num_cells = 0
            for real_account, cells, row_classes in table:
                if not isinstance(real_account, realization.RealAccount):
                    continue
                last_posting = realization.find_last_active_posting(
                    real_account.txn_postings)

                # Don't render updates to accounts that have been closed.
                # Note: this is O(N), maybe we can store this at realization.
                if last_posting is None or isinstance(last_posting,
                                                      data.Close):
                    continue

                last_date = data.get_entry(last_posting).date

                # Skip this posting if a cutoff date has been specified and the
                # account has been updated to at least the cutoff date.
                if self.args.cutoff and self.args.cutoff <= last_date:
                    continue

                # Okay, we need to render this. Append.
                cells.append(
                    data.get_entry(last_posting).date if real_account.
                    txn_postings else '-')
                num_cells += 1

            if num_cells:
                file.write(oss.getvalue())
Exemple #6
0
    def render_real_text(self, real_root, price_map, price_date, options_map,
                         file):
        rows = []
        account_types = options.get_account_types(options_map)
        for root in (account_types.assets, account_types.liabilities):
            for unused_first_line, unused_cont_line, real_account in realization.dump(
                    realization.get(real_root, root)):

                last_posting = realization.find_last_active_posting(
                    real_account.txn_postings)

                # Don't render updates to accounts that have been closed.
                # Note: this is O(N), maybe we can store this at realization.
                if last_posting is None or isinstance(last_posting,
                                                      data.Close):
                    continue

                last_date = data.get_entry(last_posting).date
                # Skip this posting if a cutoff date has been specified and the
                # account has been updated to at least the cutoff date.
                if self.args.cutoff and self.args.cutoff <= last_date:
                    continue

                rows.append((real_account.account, last_date))

        table_ = table.create_table(rows, [(0, 'Account'),
                                           (1, 'Last Date', '{}'.format)])
        table.render_table(table_, file, 'text')
    def test_is_account_active(self, entries, _, __):
        """
        2014-01-01 open Assets:Inactive
        2014-01-01 open Assets:Active
        2014-01-01 open Equity:Other

        2014-07-04 *
          Assets:Active   1 USD
          Equity:Other

        """
        real_root = realization.realize(entries)
        self.assertFalse(tree_table.is_account_active(
            realization.get(real_root, 'Assets:Inactive')))
        self.assertTrue(tree_table.is_account_active(
            realization.get(real_root, 'Assets:Active')))
Exemple #8
0
    def monthly_income_expenses_totals(self):
        month_tuples = self._interval_tuples('month', self.entries)
        monthly_totals = []
        for begin_date, end_date in month_tuples:
            entries = self._entries_in_inclusive_range(self.entries, begin_date, end_date)
            realized = realization.realize(entries, self.account_types)
            income_totals = self._table_totals(realization.get(realized, self.account_types.income))
            expenses_totals = self._table_totals(realization.get(realized, self.account_types.expenses))

            monthly_totals.append({
                'begin_date': begin_date,
                'end_date': end_date,
                'income_totals': income_totals,
                'expenses_totals': expenses_totals
            })

        return monthly_totals
Exemple #9
0
    def journal(self, account_name=None):
        if account_name:
            real_account = realization.get(self.real_accounts, account_name)
        else:
            real_account = self.real_accounts

        postings = realization.get_postings(real_account)
        return self._journal_for_postings(postings)
Exemple #10
0
def create_report(entries, options_map):
    real_root = realization.realize(entries)

    # Find the institutions from the data.
    groups, ignored_accounts = find_institutions(entries, options_map)

    # List all the asset accounts which aren't included in the report.
    oc_map = getters.get_account_open_close(entries)
    open_map = {acc: open_entry for acc, (open_entry, _) in oc_map.items()}
    for acc in sorted(ignored_accounts):
        logging.info("Ignored account: %s", acc)

    # Gather missing fields and create a report object.
    institutions = []
    for name, accounts in sorted(groups.items()):
        # Get the institution fields, which is the union of the fields for all
        # the accounts with the institution fields.
        institution_accounts = [
            acc for acc in accounts if 'institution' in open_map[acc].meta
        ]

        institution_fields = {}
        for acc in institution_accounts:
            for key, value in open_map[acc].meta.items():
                institution_fields.setdefault(key, value)
        institution_fields.pop('filename', None)
        institution_fields.pop('lineno', None)

        # Create infos for each account in this institution.
        account_reports = []
        for acc in accounts:
            account_fields = {}
            for subacc in account.parents(acc):
                open_entry = open_map[subacc]
                if 'institution' in open_entry.meta:
                    break
                account_fields.update(open_entry.meta)
            account_fields.pop('filename', None)
            account_fields.pop('lineno', None)
            for field in institution_fields:
                account_fields.pop(field, None)

            real_node = realization.get(real_root, acc)
            account_reports.append(
                AccountReport(
                    acc, open_entry.date, real_node.balance,
                    sum(1 for posting in real_node.txn_postings
                        if isinstance(posting, data.TxnPosting)),
                    account_fields))

        # Create the institution report.
        institution = InstitutionReport(name, institution_fields,
                                        account_reports)
        institutions.append(institution)

    return Report(options_map['title'], institutions)
    def test_compute_balance(self):
        real_root = create_real([('Assets:US:Bank:Checking', '100 USD'),
                                 ('Assets:US:Bank:Savings', '200 USD'),
                                 ('Assets:US:Bank', '10 USD'),
                                 ('Liabilities:Bank:CreditCard', '-500 USD')])
        balance = realization.compute_balance(real_root)
        self.assertEqual(inventory.from_string('-190 USD'), balance)

        balance = realization.compute_balance(realization.get(real_root, 'Assets:US:Bank'))
        self.assertEqual(inventory.from_string('310 USD'), balance)
Exemple #12
0
    def monthly_income_expenses_totals(self):
        month_tuples = self._interval_tuples('month', self.entries)
        monthly_totals = []
        for begin_date, end_date in month_tuples:
            entries = self._entries_in_inclusive_range(self.entries,
                                                       begin_date, end_date)
            realized = realization.realize(entries, self.account_types)
            income_totals = self._table_totals(
                realization.get(realized, self.account_types.income))
            expenses_totals = self._table_totals(
                realization.get(realized, self.account_types.expenses))

            monthly_totals.append({
                'begin_date': begin_date,
                'end_date': end_date,
                'income_totals': income_totals,
                'expenses_totals': expenses_totals
            })

        return monthly_totals
Exemple #13
0
def test_should_show(app):
    with app.test_request_context('/'):
        app.preprocess_request()
        assert should_show(g.api.root_account) is True

        assert should_show(g.api.root_account) is True
    with app.test_request_context('/?time=2100'):
        app.preprocess_request()
        assert not g.api.fava_options['show-accounts-with-zero-balance']
        assert should_show(g.api.root_account) is True
        empty_account = realization.get(g.api.root_account, 'Expenses')
        assert should_show(empty_account) is False
Exemple #14
0
    def render_real_htmldiv(self, real_root, options_map, file):
        operating_currencies = options_map['operating_currency']
        assets = tree_table.table_of_balances(
            realization.get(real_root, options_map['name_assets']),
            operating_currencies,
            self.formatter)
        liabilities = tree_table.table_of_balances(
            realization.get(real_root, options_map['name_liabilities']),
            operating_currencies,
            self.formatter)
        equity = tree_table.table_of_balances(
            realization.get(real_root, options_map['name_equity']),
            operating_currencies,
            self.formatter)

        file.write("""
               <div class="halfleft">

                 <div id="assets">
                  <h3>Assets</h3>
                  {assets}
                 </div>

               </div>
               <div class="halfright">

                 <div id="liabilities">
                  <h3>Liabilities</h3>
                  {liabilities}
                 </div>
                 <div class="spacer">
                 </div>
                 <div id="equity">
                  <h3>Equity</h3>
                  {equity}
                 </div>

               </div>
            """.format(**locals()))
Exemple #15
0
    def _real_accounts(self, account_name, entries, begin_date=None, end_date=None):
        """
        Returns the realization.RealAccount instances for account_name, and
        their entries clamped by the optional begin_date and end_date.

        Warning: For efficiency, the returned result does not include any added
        postings to account for balances at 'begin_date'.

        :return: realization.RealAccount instances
        """
        entries_in_range = self._entries_in_inclusive_range(entries, begin_date=begin_date, end_date=end_date)
        real_accounts = realization.get(realization.realize(entries_in_range, [account_name]), account_name)

        return real_accounts
 def test_get(self):
     ra0 = create_simple_account()
     self.assertEqual('Assets',
                      realization.get(ra0, 'Assets').account)
     self.assertEqual('Assets:US:Bank',
                      realization.get(ra0, 'Assets:US:Bank').account)
     self.assertEqual('Assets:US:Bank:Checking',
                      realization.get(ra0, 'Assets:US:Bank:Checking').account)
     self.assertEqual(None, realization.get(ra0, 'Assets:US:Bank:Savings'))
     self.assertEqual(42, realization.get(ra0, 'Assets:US:Bank:Savings', 42))
     with self.assertRaises(ValueError):
         self.assertEqual(42, realization.get(ra0, None))
     self.assertEqual(None, realization.get(ra0, ''))
Exemple #17
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)
Exemple #18
0
def zip_real_accounts(ra_list):
    if not ra_list:
        return
    first = ra_list[0]
    return {
        'account': first.account,
        'balance_and_balance_children':
            [(serialize_inventory(ra.balance, at_cost=True),
              serialize_inventory(realization.compute_balance(ra),
                                  at_cost=True))
             for ra in ra_list],
        'children': [zip_real_accounts([realization.get(ra, n)
                                        for ra in ra_list])
                     for n, a in sorted(first.items())],
    }
Exemple #19
0
def zip_real_accounts(ra_list):
    if not ra_list:
        return
    first = ra_list[0]
    return {
        'account':
        first.account,
        'balance_and_balance_children':
        [(serialize_inventory(ra.balance, at_cost=True),
          serialize_inventory(realization.compute_balance(ra), at_cost=True))
         for ra in ra_list],
        'children': [
            zip_real_accounts([realization.get(ra, n) for ra in ra_list])
            for n, a in sorted(first.items())
        ],
    }
Exemple #20
0
def _real_account(account_name, entries, begin_date=None, end_date=None, min_accounts=None):
    """
    Returns the realization.RealAccount instances for account_name, and
    their entries clamped by the optional begin_date and end_date.

    Warning: For efficiency, the returned result does not include any added
    postings to account for balances at 'begin_date'.

    :return: realization.RealAccount instances
    """
    if begin_date:
        entries = list(iter_entry_dates(entries, begin_date, end_date))
    if not min_accounts:
        min_accounts = [account_name]

    return realization.get(realization.realize(entries, min_accounts), account_name)
Exemple #21
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 file')
    parser.add_argument('accounts', nargs='+', help='Account names')

    parser.add_argument('--date',
                        type=date_utils.parse_date_liberally,
                        help="Date")

    parser.add_argument('-o',
                        '--output',
                        action='store',
                        help="Output directory for the CSV files")

    args = parser.parse_args()

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

    # Filter out anything after the given date.
    if args.date is None:
        args.date = entries[-1].date
    entries = [entry for entry in entries if entry.date < args.date]

    # Compute the balance in each account and process it.
    real_root = realization.realize(entries)
    rows = []
    fieldspec = list(enumerate(['Vest Date', 'Units', 'Instrument', 'Cost']))
    for account in args.accounts:
        real_acc = realization.get(real_root, account)
        if real_acc is None:
            logging.error("Account '%s' does not exist", account)
            continue
        for position in real_acc.balance:
            rows.append((position.cost.date, position.units.number,
                         position.units.currency, position.cost.number,
                         position.cost.currency))
    rows = sorted(rows)
    tbl = table.create_table(rows, fieldspec)

    table.render_table(tbl, sys.stdout, 'text')
    if args.output:
        with open(path.join(args.output), 'w') as file:
            table.render_table(tbl, file, 'csv')
Exemple #22
0
    def journal(self, account_name=None, with_change_and_balance=False, with_journal_children=True):
        if account_name:
            if not account_name in [account['full_name'] for account in self.all_accounts]:
                return []

            real_account = realization.get(self.root_account, account_name)

            if with_journal_children:
                postings = realization.get_postings(real_account)
            else:
                postings = []
                postings.extend(real_account.txn_postings)
                postings.sort(key=posting_sortkey)

            return self._journal_for_postings(postings, with_change_and_balance=with_change_and_balance)
        else:
            return self._journal_for_postings(self.entries, with_change_and_balance=with_change_and_balance)
    def get_postings(self, real_root):
        """Return the postings corresponding to the account filter option.

        Args:
          real_root: A RealAccount node for the root of all accounts.
        Returns:
          A list of posting or directive instances.
        """
        if self.args.account:
            real_account = realization.get(real_root, self.args.account)
            if real_account is None:
                raise report.ReportError("Invalid account name: {}".format(
                    self.args.account))
        else:
            real_account = real_root

        return realization.get_postings(real_account)
Exemple #24
0
    def _real_account(self, account_name, entries, begin_date=None,
                      end_date=None, min_accounts=None):
        """
        Returns the realization.RealAccount instances for account_name, and
        their entries clamped by the optional begin_date and end_date.

        Warning: For efficiency, the returned result does not include any added
        postings to account for balances at 'begin_date'.

        :return: realization.RealAccount instances
        """
        if begin_date:
            entries = list(iter_entry_dates(entries, begin_date, end_date))
        if not min_accounts:
            min_accounts = [account_name]

        return realization.get(realization.realize(entries, min_accounts),
                               account_name)
    def setUp(self, entries, _, __):
        """
        plugin "beancount.plugins.implicit_prices"

        2014-01-01 open Assets:Checking
        2014-01-01 open Assets:Investing
        2014-01-01 open Assets:Savings
        2014-01-01 open Income:MountainOfMoney
        2014-01-01 open Equity:Opening-Balances

        2014-01-05 pad Assets:Checking Equity:Opening-Balances

        2014-02-10 balance Assets:Checking   100.00 USD

        2014-03-04 ! "Salary"
          Income:MountainOfMoney    -4000.00 USD
          Assets:Checking

        2014-03-05 balance Assets:Checking   4100.00 USD

        2014-03-10 note Assets:Checking "Something to say"
        2014-03-11 document Assets:Checking "/path/to/document.pdf"

        ;; With link.
        2014-03-17 * "Transfer" ^784375fd5a68
          Assets:Checking          -2000 USD
          Assets:Checking          -1500 USD
          Assets:Investing:Cash     3500 USD

        2014-03-25 * "Investment"
          Assets:Investing:Cash      -3000 USD
          Assets:Investing:Stock        30 STOCK {100 USD}

        ;; Failing.
        2014-05-01 balance  Assets:Checking   0.00 USD

        2014-12-31 close Assets:Checking
        """
        self.entries = entries
        real_root = realization.realize(self.entries)
        self.real_account = realization.get(real_root, 'Assets:Checking')
        self.postings = realization.get_postings(self.real_account)
Exemple #26
0
    def test_realize(self):
        input_string = """
        2012-01-01 open Expenses:Restaurant
        2012-01-01 open Expenses:Movie
        2012-01-01 open Assets:Cash
        2012-01-01 open Liabilities:CreditCard
        2012-01-01 open Equity:Opening-Balances

        2012-01-15 pad Liabilities:CreditCard Equity:Opening-Balances

        2012-03-01 * "Food"
          Expenses:Restaurant     100 CAD
          Assets:Cash

        2012-03-10 * "Food again"
          Expenses:Restaurant     80 CAD
          Liabilities:CreditCard

        ;; Two postings on the same account.
        2012-03-15 * "Two Movies"
          Expenses:Movie     10 CAD
          Expenses:Movie     10 CAD
          Liabilities:CreditCard

        2012-03-20 note Liabilities:CreditCard "Called Amex, asked about 100 CAD dinner"

        2012-03-28 document Liabilities:CreditCard "{filename}"

        2013-04-01 balance Liabilities:CreditCard   204 CAD

        2014-01-01 close Liabilities:CreditCard
        """
        # 'filename' is because we need an existing filename.
        entries, errors, _ = loader.load_string(
            input_string.format(filename=__file__))

        real_account = realization.realize(entries)
        ra0_movie = realization.get(real_account, 'Expenses:Movie')
        self.assertEqual('Expenses:Movie', ra0_movie.account)
        expected_balance = inventory.Inventory()
        expected_balance.add_amount(A('20 CAD'))
        self.assertEqual(expected_balance, ra0_movie.balance)
Exemple #27
0
    def _real_accounts(self, account_name, begin_date=None, end_date=None):
        """
        Returns the realization.RealAccount instances for account_name, and their entries
        clamped by the optional begin_date and end_date.

        Returns:
            realization.RealAccount instances
        """
        begin_date_, end_date_ = getters.get_min_max_dates(self.entries, (Transaction))
        if begin_date:
            begin_date_ = begin_date
        if end_date:
            end_date_ = end_date

        entries, index = summarize.clamp_opt(self.entries, begin_date_, end_date_ + timedelta(days=1),
                                                     self.options_map)

        real_accounts = realization.get(realization.realize(entries, self.account_types), account_name)

        return real_accounts
Exemple #28
0
    def get_postings(self, real_root):
        """Return the postings corresponding to the account filter option.

        Args:
          real_root: A RealAccount node for the root of all accounts.
        Returns:
          A list of posting or directive instances.
        """
        if self.args.account:
            real_account = realization.get(real_root, self.args.account)
            if real_account is None:
                # If the account isn't found, return an empty list of postings.
                # Note that this used to return the following error.
                # raise base.ReportError(
                #     "Invalid account name: {}".format(self.args.account))
                return []
        else:
            real_account = real_root

        return realization.get_postings(real_account)
Exemple #29
0
    def _real_accounts(self,
                       account_name,
                       entries,
                       begin_date=None,
                       end_date=None):
        """
        Returns the realization.RealAccount instances for account_name, and
        their entries clamped by the optional begin_date and end_date.

        Warning: For efficiency, the returned result does not include any added
        postings to account for balances at 'begin_date'.

        :return: realization.RealAccount instances
        """
        entries_in_range = self._entries_in_inclusive_range(
            entries, begin_date=begin_date, end_date=end_date)
        real_accounts = realization.get(
            realization.realize(entries_in_range, [account_name]),
            account_name)

        return real_accounts
    def test_find_last_active_posting(self, entries, _, __):
        """
        2012-01-01 open Assets:Target
        2012-01-01 open Equity:Other

        2014-02-01 *
          Assets:Target            123.45 CAD
          Equity:Other

        2014-03-01 U "This should get ignored because of the unrealized flag."
          Assets:Target            -23.45 CAD
          Equity:Other

        ;; This should get ignored because it's not one of the directives checked for
        ;; active.
        2014-03-02 event "location" "Somewhere, Somewhereland"
        """
        real_account = realization.realize(entries)
        txn_postings = realization.get(real_account, 'Assets:Target').txn_postings
        txn_posting = realization.find_last_active_posting(txn_postings)
        self.assertEqual(datetime.date(2014, 2, 1), txn_posting.txn.date)
Exemple #31
0
    def journal(self,
                account_name=None,
                with_change_and_balance=False,
                with_journal_children=True):
        if account_name:
            if not account_name in [
                    account['full_name'] for account in self.all_accounts
            ]:
                return []

            real_account = realization.get(self.root_account, account_name)

            if with_journal_children:
                postings = realization.get_postings(real_account)
            else:
                postings = []
                postings.extend(real_account.txn_postings)
                postings.sort(key=posting_sortkey)

            return self._journal_for_postings(
                postings, with_change_and_balance=with_change_and_balance)
        else:
            return self._journal_for_postings(
                self.entries, with_change_and_balance=with_change_and_balance)
Exemple #32
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
Exemple #33
0
def test_get_or_create(example_api):
    assert get_or_create(example_api.root_account, '') == \
        example_api.root_account
    assert get_or_create(example_api.root_account, 'Expenses') == \
        realization.get(example_api.root_account, 'Expenses')
Exemple #34
0
 def monthly_totals(self, account_name):
     real_account = realization.get(self.real_accounts, account_name)
     return self._monthly_totals(real_account.account, self.entries)
Exemple #35
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
Exemple #36
0
 def yearly_totals(self, account_name):
     real_account = realization.get(self.root_account, account_name)
     return self._interval_totals(real_account.account, 'year', self.entries)
Exemple #37
0
 def yearly_totals(self, account_name):
     real_account = realization.get(self.root_account, account_name)
     return self._interval_totals(real_account.account, 'year',
                                  self.entries)
Exemple #38
0
def test_get_or_create(example_ledger: FavaLedger) -> None:
    assert (get_or_create(example_ledger.all_root_account,
                          "") == example_ledger.all_root_account)
    assert get_or_create(example_ledger.all_root_account,
                         "Expenses") == realization.get(
                             example_ledger.all_root_account, "Expenses")
Exemple #39
0
def test_get_or_create(example_ledger):
    assert (get_or_create(example_ledger.root_account,
                          "") == example_ledger.root_account)
    assert get_or_create(example_ledger.root_account,
                         "Expenses") == realization.get(
                             example_ledger.root_account, "Expenses")
Exemple #40
0
    def test_iterate_with_balance(self, entries, _, __):
        """
        2012-01-01 open Assets:Bank:Checking
        2012-01-01 open Expenses:Restaurant
        2012-01-01 open Equity:Opening-Balances

        2012-01-15 pad Assets:Bank:Checking Equity:Opening-Balances

        2012-01-20 balance Assets:Bank:Checking  20.00 USD

        2012-03-01 * "With a single entry"
          Expenses:Restaurant     11.11 CAD
          Assets:Bank:Checking

        2012-03-02 * "With two entries"
          Expenses:Restaurant     20.01 CAD
          Expenses:Restaurant     20.02 CAD
          Assets:Bank:Checking

        2012-03-02 note Expenses:Restaurant  "This was good"

        2012-04-01 balance Expenses:Restaurant  51.14 CAD
        """
        root_account = realization.realize(entries)
        real_account = realization.get(root_account, 'Expenses:Restaurant')

        def simplify_rtuple(rtuple):
            return [(type(entry), len(postings), str(change), str(balance))
                    for entry, postings, change, balance in rtuple]

        # Surprinsingly enough, this covers all the legal cases that occur in
        # practice (checked for full coverage manually if you like).
        # pylint: disable=bad-whitespace
        rtuple = realization.iterate_with_balance(
            real_account.txn_postings[:-2])
        self.assertEqual([
            (data.Open, 0, '()', '()'),
            (data.Transaction, 1, '(11.11 CAD)', '(11.11 CAD)'),
            (data.Transaction, 2, '(40.03 CAD)', '(51.14 CAD)'),
        ], simplify_rtuple(rtuple))

        # Test it again with the final balance entry.
        rtuple = realization.iterate_with_balance(real_account.txn_postings)
        self.assertEqual([
            (data.Open, 0, '()', '()'),
            (data.Transaction, 1, '(11.11 CAD)', '(11.11 CAD)'),
            (data.Transaction, 2, '(40.03 CAD)', '(51.14 CAD)'),
            (data.Note, 0, '()', '(51.14 CAD)'),
            (data.Balance, 0, '()', '(51.14 CAD)'),
        ], simplify_rtuple(rtuple))

        # Test it out with valid input but with entries for the same transaction
        # separated by another entry. Swap the balance entry with the last
        # posting entry to test this.
        postings = list(real_account.txn_postings)
        postings[-3], postings[-2] = postings[-2], postings[-3]
        rtuple = realization.iterate_with_balance(postings)
        self.assertEqual([
            (data.Open, 0, '()', '()'),
            (data.Transaction, 1, '(11.11 CAD)', '(11.11 CAD)'),
            (data.Transaction, 2, '(40.03 CAD)', '(51.14 CAD)'),
            (data.Note, 0, '()', '(51.14 CAD)'),
            (data.Balance, 0, '()', '(51.14 CAD)'),
        ], simplify_rtuple(rtuple))

        # Go one step further and test it out with invalid date ordering.
        postings = list(real_account.txn_postings)
        postings[-1], postings[-2] = postings[-2], postings[-1]
        with self.assertRaises(AssertionError):
            list(realization.iterate_with_balance(postings))
Exemple #41
0
def get_balance(account, currency, real_root):
    real_account = realization.get(real_root, account)
    subtree_balance = realization.compute_balance(real_account,
                                                  leaf_only=False)
    return subtree_balance.get_currency_units(currency)