示例#1
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))
示例#2
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)
示例#3
0
    def _transform_balance(self, truelayer_balance, account, time_txns,
                           pending_time_txns):
        """Transforms TrueLayer Balance to beancount Balance.
        
        Balance from TrueLayer can be effective at the middle of a day with
        pending transactions ignored, while beancount balance assertions
        must be applied at the beginning of a day and pending transactions
        are taken into account.

        It is not always possible to get pending transactions. If that is not
        available balance assertions may have to be corrected retrospectively.
        """

        balance_time = dateutil.parser.parse(
            truelayer_balance['update_timestamp']).astimezone()
        assertion_time = datetime.datetime.combine(balance_time,
                                                   datetime.time.min,
                                                   balance_time.tzinfo)

        txns_to_remove = [
            txn for t, txn in time_txns if assertion_time <= t < balance_time
        ]
        inventory_to_remove = inventory.Inventory()
        for txn in txns_to_remove:
            for posting in txn.postings:
                inventory_to_remove.add_position(posting)
        amount_to_remove = inventory_to_remove.get_currency_units(
            truelayer_balance['currency'])

        txns_to_add = [
            txn for t, txn in pending_time_txns if t < assertion_time
        ]
        inventory_to_add = inventory.Inventory()
        for txn in txns_to_add:
            for posting in txn.postings:
                inventory_to_add.add_position(posting)
        amount_to_add = inventory_to_add.get_currency_units(
            truelayer_balance['currency'])

        number = currency_to_decimal(truelayer_balance['current'])
        if account['liability']:
            number = -number
        number += amount_to_add.number
        number -= amount_to_remove.number
        return Balance(
            meta=new_metadata('', 0),
            date=assertion_time.date(),
            account=account['beancount_account'],
            amount=Amount(number, truelayer_balance['currency']),
            tolerance=None,
            diff_amount=None,
        )
示例#4
0
def main():
    parser = argparse.ArgumentParser(description=__doc__.strip())
    parser.add_argument('filename', help='Beancount ledger filename.')
    parser.add_argument('account', help='Account name.')
    parser.add_argument('size', nargs='?', type=Decimal,
                        help='Size to match at highest cost.')
    args = parser.parse_args()

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

    # Accumulate lots from account.
    balance = inventory.Inventory()
    for entry in data.filter_txns(entries):
        for posting in entry.postings:
            if posting.account == args.account:
                balance.add_position(posting)

    # Book most expensive shares.
    remaining_size = args.size
    booked = []
    for pos in sorted(balance, key=lambda pos: pos.cost.number, reverse=True):
        print(pos)
        if remaining_size:
            booked_size = min(remaining_size, pos.units.number)
            booked.append((booked_size, pos))
            remaining_size -= booked_size

    # Print out booked ones, if a size was specified.
    if booked:
        print()
        print("Booked:")
        for booked_size, pos in booked:
            if booked_size != pos.units.number:
                pos = pos._replace(units=pos.units._replace(number=booked_size))
            print("  {:50}  {}".format(args.account, -pos))
示例#5
0
def get_matching_entries(entries, options_map, query):
    query_text = 'SELECT * ' + query
    parser = query_parser.Parser()
    parsed_query = parser.parse(query_text)
    c_from = None
    if parsed_query.from_clause:
        c_from = query_compile.compile_from(parsed_query.from_clause, query_env.FilterEntriesEnvironment())
    c_where = None
    if parsed_query.where_clause:
        c_where = query_compile.compile_expression(parsed_query.where_clause, query_env.FilterPostingsEnvironment())

    # Figure out if we need to compute balance.
    balance = None
    if c_where and query_execute.uses_balance_column(c_where):
        balance = inventory.Inventory()

    context = query_execute.RowContext()
    context.balance = balance
    

    # Initialize some global properties for use by some of the accessors.
    context.options_map = options_map
    context.account_types = options.get_account_types(options_map)
    context.open_close_map = getters.get_account_open_close(entries)
    #context.commodity_map = getters.get_commodity_map(entries)
    context.price_map = prices.build_price_map(entries) 

    if c_from is not None:
        filtered_entries = query_execute.filter_entries(c_from, entries, options_map)
    else:
        filtered_entries = entries
    return filtered_entries
示例#6
0
    def test_compute_postings_balance(self, entries, _, __):
        """
        2014-01-01 open Assets:Bank:Checking
        2014-01-01 open Assets:Bank:Savings
        2014-01-01 open Assets:Investing
        2014-01-01 open Assets:Other

        2014-05-26 note Assets:Investing "Buying some shares"

        2014-05-30 *
          Assets:Bank:Checking  111.23 USD
          Assets:Bank:Savings   222.74 USD
          Assets:Bank:Savings   17.23 CAD
          Assets:Investing      10000 EUR
          Assets:Investing      32 HOOL {45.203 USD}
          Assets:Other          1000 EUR @ 1.78 GBP
          Assets:Other          1000 EUR @@ 1780 GBP
        """
        postings = entries[:-1] + entries[-1].postings
        computed_balance = realization.compute_postings_balance(postings)

        expected_balance = inventory.Inventory()
        expected_balance.add_amount(A('333.97 USD'))
        expected_balance.add_amount(A('17.23 CAD'))
        expected_balance.add_amount(
            A('32 HOOL'),
            position.Cost(D('45.203'), 'USD', datetime.date(2014, 5, 30),
                          None))
        expected_balance.add_amount(A('12000 EUR'))
        self.assertEqual(expected_balance, computed_balance)
示例#7
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
示例#8
0
 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
示例#9
0
    def test_compute_entries_balance_currencies(self, entries, _, __):
        """
        2014-01-01 open Assets:Bank:Checking
        2014-01-01 open Assets:Bank:Savings
        2014-01-01 open Assets:Investing
        2014-01-01 open Assets:Other

        2014-06-01 *
          Assets:Bank:Checking  111.23 USD
          Assets:Other

        2014-06-02 *
          Assets:Bank:Savings   222.74 USD
          Assets:Other

        2014-06-03 *
          Assets:Bank:Savings   17.23 CAD
          Assets:Other

        2014-06-04 *
          Assets:Investing      10000 EUR
          Assets:Other

        """
        computed_balance = interpolate.compute_entries_balance(entries)
        expected_balance = inventory.Inventory()
        self.assertEqual(expected_balance, computed_balance)
示例#10
0
 def _total_balance(self, names, begin_date, end_date):
     totals = [realization.compute_balance(
         self._real_account(account_name, self.entries, begin_date,
                            end_date))
               for account_name in names]
     return serialize_inventory(sum(totals, inventory.Inventory()),
                                at_cost=True)
示例#11
0
def main():
    parser = argparse.ArgumentParser(description=__doc__.strip())

    parser.add_argument('filename', help='Beancount filename')
    parser.add_argument('account', help='Root account to consider')

    parser.add_argument('-c',
                        '--currency',
                        action='store',
                        default='USD',
                        help="The currency to pull out of the inventory")

    args = parser.parse_args()

    # Load the Beancount input file.
    entries, _, options_map = loader.load_file(args.filename)

    # Compute monthly time intervals.
    start_date = datetime.date(2013, 1, 28)
    dateiter = iter(
        rrule.rrule(rrule.MONTHLY,
                    dtstart=datetime.datetime(2013, 1, 1),
                    until=datetime.datetime.now()))

    # Compute cumulative totals accumulated at those dates.
    curve = [(datetime.date(2013, 1, 28), Decimal())]
    date = next(dateiter).date()
    balance = inventory.Inventory()
    is_account = account.parent_matcher(args.account)
    for entry in entries:
        if entry.date >= date:
            # At the boundary, save the date and total number.
            try:
                total = -balance.get_units(args.currency).number
                curve.append((date, total))
                date = next(dateiter).date()
            except StopIteration:
                break

        # Sum up the amounts from those accounts.
        if isinstance(entry, data.Transaction):
            for posting in entry.postings:
                if is_account(posting.account):
                    balance.add_position(posting.position)

    # Compute multiple averages over fixed windows of a number of months and
    # plot them.
    months = [None, 12, 6, 3]
    for num in months:
        series = rolling_average(curve, num)
        pyplot.plot([date for date, total in series],
                    [total for date, total in series],
                    label=str(num))
        print('{:10}: {:10,.2f}'.format(num if num is not None else 0,
                                        series[-1][1]))

    # Show that joint plot.
    pyplot.legend()
    pyplot.tight_layout()
    pyplot.show()
示例#12
0
 def test_creating__with_date(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'),
         position.from_string('10 HOOL {500 USD / 2014-02-11}'))
     self.assertEqual(
         inventory.from_string('10 HOOL {500 USD / 2014-02-11}'), inv)
示例#13
0
 def test_reducing__sans_date__equal_amount(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('10 HOOL {500 USD}'))
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('-10 HOOL {500 USD}'))
     self.assertTrue(inv.is_empty())
示例#14
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))
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
示例#16
0
 def test_reducing__sans_date__over_amount(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('10 HOOL {500 USD}'))
     with self.assertRaises(ValueError):
         wash_calculator.book_position(
             inv, DD('2014-02-10'),
             position.from_string('-13 HOOL {500 USD}'))
示例#17
0
 def test_compile_EvalCost(self):
     with self.assertRaises(qc.CompilationError):
         qe.CostPosition([qc.EvalConstant(17)])
     with self.assertRaises(qc.CompilationError):
         qe.CostPosition([qc.EvalConstant(inventory.Inventory())])
     c_cost = qe.CostPosition(
         [qc.EvalConstant(position.Position.from_string('100 USD'))])
     self.assertEqual(amount.Amount, c_cost.dtype)
示例#18
0
 def test_reducing__sans_date__incomplete_amount(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('10 HOOL {500 USD}'))
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('-7 HOOL {500 USD}'))
     self.assertEqual(
         inventory.from_string('3 HOOL {500 USD / 2014-02-10}'), inv)
示例#19
0
 def test_augmenting__sans_date(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('10 HOOL {500 USD}'))
     wash_calculator.book_position(inv, DD('2014-02-10'),
                                   position.from_string('7 HOOL {500 USD}'))
     self.assertEqual(
         inventory.from_string('17 HOOL {500 USD / 2014-02-10}'), inv)
示例#20
0
 def test_reducing__with_diff_date__incomplete_amount(self):
     inv = inventory.Inventory()
     wash_calculator.book_position(
         inv, DD('2014-02-10'), position.from_string('10 HOOL {500 USD}'))
     with self.assertRaises(ValueError):
         wash_calculator.book_position(
             inv, DD('2014-02-10'),
             position.from_string('-7 HOOL {500 USD / 2014-02-11}'))
示例#21
0
 def test_segregate_units(self):
     inv = I('2.2 HOOL {532.43 USD}, '
             '2.3 HOOL {564.00 USD, 2015-07-14}, '
             '3.41 CAD, 101.20 USD')
     ccymap = inv.segregate_units(['HOOL', 'USD', 'EUR'])
     self.assertEqual({
         None: I('3.41 CAD'),
         'USD': I('101.20 USD'),
         'EUR': inventory.Inventory(),
         'HOOL': I('2.2 HOOL {532.43 USD}, '
                                       '2.3 HOOL {564.00 USD, 2015-07-14}')}, ccymap)
示例#22
0
    def __init__(self, account_name, *args, **kwargs):
        """Create a RealAccount instance.

        Args:
          account_name: a string, the name of the account. Maybe not be None.
        """
        super().__init__(*args, **kwargs)
        assert isinstance(account_name, str)
        self.account = account_name
        self.txn_postings = []
        self.balance = inventory.Inventory()
示例#23
0
def compute_balance(real_account):
    """Compute the total balance of this account and all its subaccounts.

    Args:
      real_account: A RealAccount instance.
    Returns:
      An Inventory.
    """
    total_balance = inventory.Inventory()
    for real_account in iter_children(real_account):
        total_balance += real_account.balance
    return total_balance
示例#24
0
def remove_commissions(entries, unused_options_map, config):
    """Remove the commissions from the P/L of closing/sales transactions."""

    try:
        commission_regexp, income_regexp, outgoing_account = parse_config(
            config)
    except ValueError:
        return [], [
            Error(None, "Invalid configuration for {} plugin".format(__file__),
                  None)
        ]

    new_entries = []
    for entry in entries:
        # Match the transaction.
        if (isinstance(entry, data.Transaction) and any(
                income_regexp.match(posting.account)
                for posting in entry.postings) and any(
                    commission_regexp.match(posting.account)
                    for posting in entry.postings)):

            # Find the commissions amounts.
            commissions = inventory.Inventory()
            for posting in entry.postings:
                if commission_regexp.match(posting.account):
                    commissions.add_amount(posting.units)

            # Find the first income account.
            for posting in entry.postings:
                if income_regexp.match(posting.account):
                    income_account = posting.account
                    break
            assert income_account, "Income account not found."

            # Insert the new legs.
            new_postings = []
            for cposition in commissions:
                new_postings.extend([
                    data.Posting(income_account, cposition.units, None, None,
                                 None, None),
                    data.Posting(outgoing_account, -cposition.units, None,
                                 None, None, None),
                ])

                # Distribute the commission.
                distribute_commission_on_metadata(cposition.units,
                                                  entry.postings)

            entry = entry._replace(postings=entry.postings + new_postings)

        new_entries.append(entry)

    return new_entries, []
示例#25
0
def create_row_context(entries, options_map):
    """Create the context container which we will use to evaluate rows."""
    context = RowContext()
    context.balance = inventory.Inventory()

    # Initialize some global properties for use by some of the accessors.
    context.options_map = options_map
    context.account_types = options.get_account_types(options_map)
    context.open_close_map = getters.get_account_open_close(entries)
    context.commodity_map = getters.get_commodity_directives(entries)
    context.price_map = prices.build_price_map(entries)

    return context
示例#26
0
def get_postings_table(entries: data.Entries, options_map: Dict,
                       accounts_map: Dict[str, data.Open],
                       threshold: Decimal = D('0.01')) -> Table:
    """Enumerate all the postings."""
    header = ['account',
              'account_abbrev',
              'number',
              'currency',
              'cost_number',
              'cost_currency',
              'cost_date']
    balances, _ = summarize.balance_by_account(entries)
    acctypes = options.get_account_types(options_map)
    rows = []
    for acc, balance in sorted(balances.items()):
        # Keep only the balance sheet accounts.
        acctype = account_types.get_account_type(acc)
        if not acctype in (acctypes.assets, acctypes.liabilities):
            continue

        # If the account has "NONE" booking method, merge all its postings
        # together in order to obtain an accurate cost basis and balance of
        # units.
        #
        # (This is a complex issue.) If you accrued positions without having them
        # booked properly against existing cost bases, you have not properly accounted
        # for the profit/loss to other postings. This means that the resulting
        # profit/loss is merged in the cost basis of the positive and negative
        # postings.
        dopen = accounts_map.get(acc, None)
        if dopen is not None and dopen.booking is data.Booking.NONE:
            average_balance = balance.average()
            balance = inventory.Inventory(pos
                                          for pos in average_balance
                                          if pos.units.number >= threshold)

        # Create a posting for each of the positions.
        for pos in balance:
            acc_abbrev = abbreviate_account(acc, accounts_map)
            row = [acc,
                   acc_abbrev,
                   pos.units.number,
                   pos.units.currency,
                   pos.cost.number if pos.cost else ONE,
                   pos.cost.currency if pos.cost else pos.units.currency,
                   pos.cost.date if pos.cost else None]
            rows.append(row)

    return Table(header, rows)
示例#27
0
def get_neutralizing_postings(curmap, base_account, new_accounts):
    """Process an entry.

    Args:
      curmap: A dict of currency to a list of Postings of this transaction.
      base_account: A string, the root account name to insert.
      new_accounts: A set, a mutable accumulator of new account names.
    Returns:
      A modified entry, with new postings inserted to rebalance currency trading
      accounts.
    """
    new_postings = []
    for currency, postings in curmap.items():
        # Compute the per-currency balance.
        inv = inventory.Inventory()
        for posting in postings:
            inv.add_amount(convert.get_cost(posting))
        if inv.is_empty():
            new_postings.extend(postings)
            continue

        # Re-insert original postings and remove price conversions.
        #
        # Note: This may cause problems if the implicit_prices plugin is
        # configured to run after this one, or if you need the price annotations
        # for some scripting or serious work.
        #
        # FIXME: We need to handle these important cases (they're not frivolous,
        # this is a prototype), probably by inserting some exceptions with
        # collaborating code in the booking (e.g. insert some metadata that
        # disables price conversions on those postings).
        #
        # FIXME(2): Ouch! Some of the residual seeps through here, where there
        # are more than a single currency block. This needs fixing too. You can
        # easily mitigate some of this to some extent, by excluding transactions
        # which don't have any price conversion in them.
        for pos in postings:
            if pos.price is not None:
                pos = pos._replace(price=None)
            new_postings.append(pos)

        # Insert the currency trading accounts postings.
        amount = inv.get_only_position().units
        acc = account.join(base_account, currency)
        new_accounts.add(acc)
        new_postings.append(Posting(acc, -amount, None, None, None, None))

    return new_postings
示例#28
0
 def __call__(self, context):
     args = self.eval_args(context)
     inventory_, currency = args
     converted_inventory = inventory.Inventory()
     for position_ in inventory_:
         amount_ = position_.get_cost()
         converted_amount = prices.convert_amount(context.price_map,
                                                  currency, amount_)
         if converted_amount is None:
             logging.warn(
                 'Could not convert Inventory position "{}" to USD'.format(
                     amount_))
             converted_inventory.add_amount(amount_)
         else:
             converted_inventory.add_amount(converted_amount)
     return converted_inventory
示例#29
0
    def test_render_inventory(self):
        formatter = html_formatter.HTMLFormatter(
            display_context.DEFAULT_DISPLAY_CONTEXT)
        balance = inventory.Inventory()
        self.assertEqual('', formatter.render_inventory(balance))

        balance = inventory.Inventory.from_string(
            '111 USD, 222 CAD, 3 HOOL {400 USD}')
        html_balance = formatter.render_inventory(balance)
        self.assertRegex(html_balance, r'\b111\b')
        self.assertRegex(html_balance, r'\bUSD\b')
        self.assertRegex(html_balance, r'\b222\b')
        self.assertRegex(html_balance, r'\bCAD\b')
        self.assertRegex(html_balance, r'\b3\b')
        self.assertRegex(html_balance, r'\bHOOL\b')
        self.assertRegex(html_balance, r'\b400\b')
示例#30
0
def compute_postings_balance(txn_postings):
    """Compute the balance of a list of Postings's or TxnPosting's positions.

    Args:
      postings: A list of Posting instances and other directives (which are
        skipped).
    Returns:
      An Inventory.
    """
    final_balance = inventory.Inventory()
    for txn_posting in txn_postings:
        if isinstance(txn_posting, Posting):
            final_balance.add_position(txn_posting)
        elif isinstance(txn_posting, TxnPosting):
            final_balance.add_position(txn_posting.posting)
    return final_balance