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)
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)
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))))
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
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)
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(""))
def leaf(acc): """Get the name of the leaf subaccount.""" return account.leaf(acc)
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
def __call__(self, context): args = self.eval_args(context) return account.leaf(args[0])