Beispiel #1
0
 def render_account(self, account_name):
     """See base class."""
     if self.view_links:
         if self.leaf_only:
             # Calculate the number of components to figure out the indent to
             # render at.
             components = account.split(account_name)
             indent = '{:.1f}'.format(
                 len(components) * self.EMS_PER_COMPONENT)
             anchor = '<a href="{}" class="account">{}</a>'.format(
                 self.build_url(
                     'journal',
                     account_name=self.account_xform.render(account_name)),
                 account.leaf(account_name))
             return '<span "account" style="padding-left: {}em">{}</span>'.format(
                 indent, anchor)
         else:
             anchor = '<a href="{}" class="account">{}</a>'.format(
                 self.build_url(
                     'journal',
                     account_name=self.account_xform.render(account_name)),
                 account_name)
             return '<span "account">{}</span>'.format(anchor)
     else:
         if self.leaf_only:
             # Calculate the number of components to figure out the indent to
             # render at.
             components = account.split(account_name)
             indent = '{:.1f}'.format(
                 len(components) * self.EMS_PER_COMPONENT)
             account_name = account.leaf(account_name)
             return '<span "account" style="padding-left: {}em">{}</span>'.format(
                 indent, account_name)
         else:
             return '<span "account">{}</span>'.format(account_name)
Beispiel #2
0
def process_account_entries(entries: data.Entries, options_map: data.Options,
                            account: Account) -> AccountData:
    """Process a single account."""
    logging.info("Processing account: %s", account)

    # Extract the relevant transactions.
    transactions = transactions_for_account(entries, account)
    if not transactions:
        logging.warning("No transactions for %s; skipping.", account)
        return transactions, None, None

    # Categorize the set of accounts encountered in the filtered transactions.
    seen_accounts = {
        posting.account
        for entry in transactions for posting in entry.postings
    }
    atypes = options.get_account_types(options_map)
    catmap = categorize_accounts(account, seen_accounts, atypes)

    # Process each of the transactions, adding derived values as metadata.
    cash_flows = []
    balance = Inventory()
    decorated_transactions = []
    for entry in transactions:

        # Update the total position in the asset we're interested in.
        positions = []
        for posting in entry.postings:
            category = catmap[posting.account]
            if category is Cat.ASSET:
                balance.add_position(posting)
                positions.append(posting)

        # Compute the signature of the transaction.
        entry = copy_and_normalize(entry)
        signature = compute_transaction_signature(catmap, entry)
        entry.meta["signature"] = signature
        entry.meta["description"] = KNOWN_SIGNATURES[signature]

        # Compute the cash flows associated with the transaction.
        flows = produce_cash_flows(entry)
        entry.meta['cash_flows'] = flows

        cash_flows.extend(
            flow._replace(balance=copy.deepcopy(balance)) for flow in flows)
        decorated_transactions.append(entry)

    currency = accountlib.leaf(account)

    cost_currencies = set(cf.amount.currency for cf in cash_flows)
    assert len(cost_currencies) == 1, str(cost_currencies)
    cost_currency = cost_currencies.pop()

    commodity_map = getters.get_commodity_directives(entries)
    comm = commodity_map[currency]

    return AccountData(account, currency, cost_currency, comm, cash_flows,
                       decorated_transactions, catmap)
Beispiel #3
0
def find_accounts(entries: data.Entries, options_map: data.Options,
                  start_date: Optional[Date]) -> List[Account]:
    """Return a list of account names from the balance sheet which either aren't
    closed or are closed now but were still open at the given start date.
    """
    commodities = getters.get_commodity_directives(entries)
    open_close_map = getters.get_account_open_close(entries)
    atypes = options.get_account_types(options_map)
    return sorted(
        account for account, (_open, _close) in open_close_map.items()
        if (accountlib.leaf(account) in commodities
            and acctypes.is_balance_sheet_account(account, atypes)
            and not acctypes.is_equity_account(account, atypes) and (
                _close is None or (start_date and _close.date > start_date))))
Beispiel #4
0
def get_root_accounts(postings):
    """Compute a mapping of accounts to root account name.

    This removes sub-accounts where the leaf of the account name is equal to the
    currency held within, and all other leaf accounts of such root accounts.

    Args:
      postings: A list of Posting instances.
    Returns:
      A dict of account names to root account names.
    """
    roots = {posting.account: (
        account.parent(posting.account)
        if (account.leaf(posting.account) == posting.units.currency or
            (account.leaf(posting.account) == "Cash" and
             posting.account != "Assets:Cash"))
        else posting.account)
             for posting in postings}
    values = set(roots.values())
    for root in list(roots):
        parent = account.parent(root)
        if parent in values:
            roots[root] = parent
    return roots
Beispiel #5
0
def infer_investments_configuration(entries: data.Entries,
                                    account_list: List[Account],
                                    out_config: InvestmentConfig):
    """Infer a reasonable configuration for input."""

    all_accounts = set(getters.get_account_open_close(entries))

    for account in account_list:
        aconfig = out_config.investment.add()
        aconfig.currency = accountlib.leaf(account)
        aconfig.asset_account = account

        regexp = re.compile(
            re.sub(r"^[A-Z][^:]+:", "[A-Z][A-Za-z0-9]+:", account) +
            ":Dividends?")
        for maccount in filter(regexp.match, all_accounts):
            aconfig.dividend_accounts.append(maccount)

        match_accounts = set()
        match_accounts.add(aconfig.asset_account)
        match_accounts.update(aconfig.dividend_accounts)
        match_accounts.update(aconfig.match_accounts)

        # Figure out the total set of accounts seed in those transactions.
        cash_accounts = set()
        for entry in data.filter_txns(entries):
            if any(posting.account in match_accounts
                   for posting in entry.postings):
                for posting in entry.postings:
                    if (posting.account == aconfig.asset_account
                            or posting.account in aconfig.dividend_accounts
                            or posting.account in aconfig.match_accounts):
                        continue
                    if (re.search(r":(Cash|Checking|Receivable|GSURefund)$",
                                  posting.account) or re.search(
                                      r"Receivable|Payable", posting.account)
                            or re.match(r"Income:.*:(Match401k)$",
                                        posting.account)):
                        cash_accounts.add(posting.account)
        aconfig.cash_accounts.extend(cash_accounts)
Beispiel #6
0
 def test_leaf(self):
     self.assertEqual("Computer", account.leaf("Expenses:Toys:Computer"))
     self.assertEqual("Toys", account.leaf("Expenses:Toys"))
     self.assertEqual("Expenses", account.leaf("Expenses"))
     self.assertEqual(None, account.leaf(""))
Beispiel #7
0
def leaf(acc):
    """Get the name of the leaf subaccount."""
    return account.leaf(acc)
Beispiel #8
0
def categorize_accounts(account: Account, accounts: Set[Account],
                        atypes: tuple) -> Dict[Account, Cat]:
    """Categorize the type of accounts for a particular stock. Our purpose is to
    make the types of postings generic, so they can be categorized and handled
    generically later on.

    The patterns used in this file depend on the particular choices of account
    names in my chart of accounts, and for others to use this, this needs to be
    specialized somewhat.
    """
    accpath = accountlib.join(*accountlib.split(account)[1:-1])
    currency = accountlib.leaf(account)

    accounts = set(accounts)
    catmap = {}

    def move(acc, category):
        if acc in accounts:
            accounts.remove(acc)
            catmap[acc] = category

    # The account itself.
    move(account, Cat.ASSET)

    # The adjacent cash account.
    move(accountlib.join(atypes.assets, accpath, "Cash"), Cat.CASH)

    # The adjacent P/L and interest account.
    move(accountlib.join(atypes.income, accpath, "PnL"), Cat.PNL)
    move(accountlib.join(atypes.income, accountlib.parent(accpath), "PnL"),
         Cat.PNL)
    move(accountlib.join(atypes.income, accpath, "Interest"), Cat.INTEREST)

    # The associated dividend account.
    move(accountlib.join(atypes.income, accpath, currency, "Dividend"),
         Cat.DIVIDEND)

    # Rounding error account.
    move("Equity:RoundingError", Cat.ROUNDING)
    move("Expenses:Losses", Cat.ROUNDING)
    move("Income:US:MSSB:RoundingVariance", Cat.ROUNDING)

    for acc in list(accounts):

        # Employer match and corresponding tracking accounts in IRAUSD.
        if re.match(
                "({}|{}):.*:Match401k".format(atypes.assets, atypes.expenses),
                acc):
            move(acc, Cat.TRACKING)
        elif re.search(r"\b(Vested|Unvested)", acc):
            move(acc, Cat.TRACKING)

        # Dividends for other stocks.
        elif re.search(r":Dividends?$", acc):
            move(acc, Cat.OTHERDIVIDEND)

        # Direct contribution from employer.
        elif re.match("{}:.*:Match401k$".format(atypes.income), acc):
            move(acc, Cat.CASH)
        elif re.search(":GSURefund$", acc):
            move(acc, Cat.CASH)

        # Expenses accounts.
        elif acc.startswith(atypes.expenses):
            move(acc, Cat.EXPENSES)
        elif acc.endswith(":Commissions"):  # Income..Commissions
            move(acc, Cat.EXPENSES)

        # Currency accounts.
        elif acc.startswith("Equity:CurrencyAccounts"):
            move(acc, Cat.CONVERSIONS)

        # Other cash or checking accounts.
        elif acc.endswith(":Cash"):
            move(acc, Cat.CASH)
        elif acc.endswith(":Checking"):
            move(acc, Cat.CASH)
        elif acc.endswith("Receivable"):
            move(acc, Cat.CASH)

        # Other stock.
        elif re.match("{}:[A-Z_.]+$".format(accountlib.parent(acc)), acc):
            move(acc, Cat.OTHERASSET)

        else:
            print("ERROR: Unknown account: {}".format(acc))
            move(acc, Cat.UNKNOWN)

    return catmap
Beispiel #9
0
 def __call__(self, context):
     args = self.eval_args(context)
     return account.leaf(args[0])