예제 #1
0
class HoldingsReport(base.TableReport):
    """The full list of holdings for Asset and Liabilities accounts."""

    names = ['holdings']

    aggregations = {
        'commodity': dict(aggregation_key=lambda holding: holding.currency),

        'account': dict(aggregation_key=lambda holding: holding.account),

        'root-account': dict(
            aggregation_key=lambda holding: account.root(3, holding.account),
            sort_key=lambda holding: holding.market_value or ZERO),

        'currency': dict(aggregation_key=lambda holding: holding.cost_currency),
        }

    def __init__(self, *rest, **kwds):
        super().__init__(*rest, **kwds)
        if self.args.relative and not self.args.currency:
            self.parser.error("--relative needs to have --currency set")

    @classmethod
    def add_args(cls, parser):
        parser.add_argument('-c', '--currency',
                            action='store', default=None,
                            help="Which currency to convert all the holdings to")

        parser.add_argument('-r', '--relative',
                            action='store_true',
                            help="True if we should render as relative values only")

        parser.add_argument('-g', '--groupby', '--by',
                            action='store', default=None,
                            choices=cls.aggregations.keys(),
                            help="How to group the holdings (default is: don't group)")

    def generate_table(self, entries, errors, options_map):
        keywords = self.aggregations[self.args.groupby] if self.args.groupby else {}
        return report_holdings(self.args.currency, self.args.relative,
                               entries, options_map,
                               **keywords)

    def render_beancount(self, entries, errors, options_map, file):
        # Don't allow any aggregations if we output as beancount format.
        for attribute in 'currency', 'relative', 'groupby':
            if getattr(self.args, attribute):
                self.parser.error(
                    "'beancount' format does not support --{} option".format(attribute))

        # Get the summarized entries and print them out.
        holdings_entries = get_holdings_entries(entries, options_map)
        dcontext = options_map['dcontext']
        printer.print_entries(holdings_entries, dcontext, file=file)
예제 #2
0
 def test_root(self):
     name = "Liabilities:US:Credit-Card:Blue"
     self.assertEqual("", account.root(0, name))
     self.assertEqual("Liabilities", account.root(1, name))
     self.assertEqual("Liabilities:US", account.root(2, name))
     self.assertEqual("Liabilities:US:Credit-Card", account.root(3, name))
     self.assertEqual("Liabilities:US:Credit-Card:Blue", account.root(4, name))
     self.assertEqual("Liabilities:US:Credit-Card:Blue", account.root(5, name))
예제 #3
0
    def option(self, filename, lineno, key, value):
        """Process an option directive.

        Args:
          filename: current filename.
          lineno: current line number.
          key: option's key (str)
          value: option's value
        """
        if key not in self.options:
            meta = new_metadata(filename, lineno)
            self.errors.append(
                ParserError(meta, "Invalid option: '{}'".format(key), None))

        elif key in options.READ_ONLY_OPTIONS:
            meta = new_metadata(filename, lineno)
            self.errors.append(
                ParserError(meta, "Option '{}' may not be set".format(key),
                            None))

        else:
            option_descriptor = options.OPTIONS[key]

            # Issue a warning if the option is deprecated.
            if option_descriptor.deprecated:
                meta = new_metadata(filename, lineno)
                self.errors.append(
                    DeprecatedError(meta, option_descriptor.deprecated, None))

            # Convert the value, if necessary.
            if option_descriptor.converter:
                try:
                    value = option_descriptor.converter(value)
                except ValueError as exc:
                    meta = new_metadata(filename, lineno)
                    self.errors.append(
                        ParserError(
                            meta, "Error for option '{}': {}".format(key, exc),
                            None))
                    return

            option = self.options[key]
            if isinstance(option, list):
                # Append to a list of values.
                option.append(value)

            elif isinstance(option, dict):
                # Set to a dict of values.
                if not (isinstance(value, tuple) and len(value) == 2):
                    self.errors.append(
                        ParserError(
                            meta,
                            "Error for option '{}': {}".format(key,
                                                               value), None))
                    return
                dict_key, dict_value = value
                option[dict_key] = dict_value

            elif isinstance(option, bool):
                # Convert to a boolean.
                if not isinstance(value, bool):
                    value = (value.lower() in {'true', 'on'}) or (value == '1')
                self.options[key] = value

            else:
                # Fix up account_rounding to be a subaccount if the user specified a
                # full account name. This is intended to ease transition in the
                # change of semantics that occurred on 2015-09-05, whereby the value
                # of this option became defined as a subaccount of Equity instead of
                # a full account name. See Issue #67.
                # This should eventually be deprecated, say, in a year (after Sep 2016).
                if key == 'account_rounding':
                    root = account.root(1, value)
                    if root in (self.options['name_{}'.format(name)]
                                for name in [
                                    'assets', 'liabilities', 'equity',
                                    'income', 'expenses'
                                ]):
                        self.errors.append(
                            ParserError(
                                self.get_lexer_location(),
                                "'account_rounding' option should now refer to "
                                "a subaccount.", None))
                        value = account.sans_root(value)

                # Set the value.
                self.options[key] = value

            # Refresh the list of valid account regexps as we go along.
            if key.startswith('name_'):
                # Update the set of valid account types.
                self.account_regexp = valid_account_regexp(self.options)
예제 #4
0
def root(acc, n):
    """Get the root name(s) of the account."""
    return account.root(n, acc)
예제 #5
0
def get_asset(entry):
    # returns the asset/liability account of a transactino
    for post in entry.postings:
        if acc.root(1, post.account) in ['Assets', 'Liabilities']:
            return post
    raise Exception(f"entry did not have Asset/ Liability posting: {entry}")
예제 #6
0
def get_income(entry):
    # returns the income/expense account of a transactino
    for post in entry.postings:
        if acc.root(1, post.account) in ['Income', 'Expenses']:
            return post
    raise Exception(f"entry did not have Income/ Expense posting: {entry}")
예제 #7
0
 def __call__(self, context):
     args = self.eval_args(context)
     return account.root(args[1], args[0])