def extract_balances_and_prices(self, file, counter):
        new_entries = []
        date = self.get_max_transaction_date()
        if date:
            # balance assertions are evaluated at the beginning of the date, so move it to the following day
            date += datetime.timedelta(days=1)
        else:
            print(
                "Warning: no transactions, using statement date for balance assertions."
            )

        settlement_fund_balance = 0
        for pos in self.get_balance_positions():
            ticker, ticker_long_name = self.get_ticker_info(pos.security)
            meta = data.new_metadata(file.name, next(counter))

            # if there are no transactions, use the date in the source file for the balance. This gives us the
            # bonus of an updated, recent balance assertion
            bal_date = date if date else pos.date.date()
            balance_entry = data.Balance(
                meta, bal_date,
                self.commodity_leaf(self.config['main_account'], ticker),
                amount.Amount(pos.units, ticker), None, None)
            new_entries.append(balance_entry)
            if ticker in self.money_market_funds:
                settlement_fund_balance = pos.units

            # extract price info if available
            if hasattr(pos, 'unit_price') and hasattr(pos, 'date'):
                meta = data.new_metadata(file.name, next(counter))
                price_entry = data.Price(
                    meta, pos.date.date(), ticker,
                    amount.Amount(pos.unit_price, self.currency))
                new_entries.append(price_entry)

        # ----------------- available cash
        available_cash = self.get_available_cash()
        if available_cash is not False:
            try:
                balance = self.get_available_cash() - settlement_fund_balance
                meta = data.new_metadata(file.name, next(counter))
                bal_date = date if date else self.file_date(file).date()
                balance_entry = data.Balance(
                    meta, bal_date, self.cash_account,
                    amount.Amount(balance, self.currency), None, None)
                new_entries.append(balance_entry)
            except AttributeError:  # self.get_available_cash()
                pass

        return new_entries
Exemple #2
0
    def extract(self, file, existing_entries=None):
        # Open the CSV file and create directives.
        entries = []
        index = 0
        with open(file.name, 'rb') as f:
            eml = parser.BytesParser().parse(fp=f)
            b = base64.b64decode(eml.get_payload()[0].get_payload())
            d = BeautifulSoup(b, "lxml")
            date_range = d.findAll(text=re.compile(
                '\d{4}\/\d{1,2}\/\d{1,2}-\d{4}\/\d{1,2}\/\d{1,2}'))[0]
            transaction_date = dateparse(
                date_range.split('-')[1].split('(')[0]).date()
            balance = '-' + d.find(src="https://pbdw.ebank.cmbchina.com/"
                                   "cbmresource/22/dyzd/jpkdyzd/xbgbdt/bqhkzz.jpg")\
                .parent.parent.find_next_sibling(
                'td').select('font')[0].text.replace('¥', '').replace(',', '').strip()
            txn_balance = data.Balance(account=self.account_name,
                                       amount=Amount(D(balance), 'CNY'),
                                       meta=data.new_metadata(".", 1000),
                                       tolerance=None,
                                       diff_amount=None,
                                       date=transaction_date)
            entries.append(txn_balance)

            bands = d.select('#fixBand29 #loopBand2>table>tr')
            for band in bands:
                tds = band.select('td #fixBand15 table table td')
                if len(tds) == 0:
                    continue
                trade_date = tds[1].text.strip()
                if trade_date == '':
                    trade_date = tds[2].text.strip()
                # date = datetime.strptime(trade_date,'%m%d').replace(year=transaction_date.year).date()
                date = datetime.strptime(trade_date, '%m%d')
                if date.month == 12 and transaction_date.month == 1:
                    date = date.replace(year=transaction_date.year - 1).date()
                else:
                    date = date.replace(year=transaction_date.year).date()
                full_descriptions = tds[3].text.strip().split('-')
                payee = full_descriptions[0]
                narration = '-'.join(full_descriptions[1:])
                real_currency = 'CNY'
                real_price = tds[4].text.replace('¥', '').replace('\xa0',
                                                                  '').strip()
                # print("Importing {} at {}".format(narration, date))
                flag = "*"
                amount = -Amount(D(real_price), real_currency)
                meta = data.new_metadata(file.name, index)
                txn = data.Transaction(
                    meta, date, self.FLAG, payee, narration, data.EMPTY_SET,
                    data.EMPTY_SET, [
                        data.Posting(self.account_name, amount, None, None,
                                     None, None),
                    ])

                entries.append(txn)

        # Insert a final balance check.

        return entries
    def extract(self, file):
        #parse csv file
        if self.file_format_version == 1:
            buchungstag, auftraggeber_empfaenger, buchungstext, verwendungszweck, betrag, kontostand, indices, endsaldo = parse_csv_file_v1(
                file.name)
        elif self.file_format_version == 2:
            buchungstag, auftraggeber_empfaenger, buchungstext, verwendungszweck, betrag, kontostand, indices, endsaldo = parse_csv_file_v2(
                file.name)
        else:
            raise IOError("Unknown file format.")
        #create transactions
        entries = []
        for i in range(len(buchungstag)):
            postings = self.guess_postings(auftraggeber_empfaenger[i],
                                           float(betrag[i]))
            meta = data.new_metadata(file.name, indices[i])
            txn = data.Transaction(meta, buchungstag[i], self.flag,
                                   auftraggeber_empfaenger[i],
                                   verwendungszweck[i], data.EMPTY_SET,
                                   data.EMPTY_SET, postings)
            entries.append(txn)
        #create balance
        meta = data.new_metadata(file.name, endsaldo[2])
        entries.append(
            data.Balance(meta, endsaldo[0] + datetime.timedelta(days=1),
                         self.account,
                         amount.Amount(D(endsaldo[1]),
                                       self.currency), None, None))

        return entries
    def add_account(self, name, date=None, currency=None, initial_amount=None):
        date = date or config.DEFAULT_DATE
        name = self._format_account_name(name)
        self.accounts.append(name)

        currencies = currency and [currency]

        self.entries.append(
            bc.Open(self._get_meta(), date, name, currencies, booking=None))

        if initial_amount is not None and initial_amount != 0:
            assert currency is not None

            self.pad_balances.append(
                bc.Pad(self._get_meta(),
                       date=config.DEFAULT_PAD_DATE,
                       account=name,
                       source_account=config.OPENING_BALANCE_ACCOUNT))
            self.pad_balances.append(
                bc.Balance(self._get_meta(),
                           date=config.DEFAULT_BALANCE_DATE,
                           account=name,
                           amount=bc.Amount(initial_amount, currency),
                           tolerance=None,
                           diff_amount=None))

        return len(self.accounts) - 1  # index of the account on self.accounts
Exemple #5
0
    def entry_from_gl(self, entries: typing.List[Entry]) -> typing.Iterable:
        """Given a single GL_ID and as a list of Entrys
        """
        first = entries[0]

        postings = []
        all_tags = set()
        all_meta = {'lineno': 0, 'filename': "", 'gl-id': first.gl_id}
        all_links = set()

        for entry in entries:
            self.new_accounts.add(entry.account)

            if first.entry_type == "Balance":
                if entry.amount and entry.date.year == 2016:
                    yield data.Pad(date=entry.date -
                                   datetime.timedelta(days=1),
                                   account=entry.account,
                                   source_account="Equity:OpeningBalances",
                                   meta={
                                       'lineno': 0,
                                       'filename': '',
                                       'note': entry.narration,
                                       'gl-id': entry.gl_id
                                   })
                yield data.Balance(date=entry.date,
                                   amount=data.Amount(entry.amount,
                                                      entry.currency),
                                   account=entry.account,
                                   tolerance=None,
                                   diff_amount=None,
                                   meta={
                                       'lineno': 0,
                                       'filename': '',
                                       'note': entry.narration,
                                       'gl-id': entry.gl_id
                                   })
            else:
                posting = data.Posting(entry.account,
                                       data.Amount(entry.amount,
                                                   entry.currency),
                                       None,
                                       None,
                                       flag='*',
                                       meta=entry.meta)
                all_tags.update(entry.tags)
                all_links.update(entry.links)

                postings.append(posting)
                all_meta.update(posting.meta or {})

        if postings:
            yield data.Transaction(meta=all_meta,
                                   date=entry.date,
                                   flag='*',
                                   payee=entry.payee,
                                   narration=entry.narration,
                                   tags=all_tags,
                                   links=all_links,
                                   postings=postings)
Exemple #6
0
def check_closing(entries, options_map):
    """Expand 'closing' metadata to a zero balance check.

    Args:
      entries: A list of directives.
      unused_options_map: An options map.
    Returns:
      A list of new errors, if any were found.
    """
    new_entries = []
    for entry in entries:
        if isinstance(entry, data.Transaction):
            for i, posting in enumerate(entry.postings):
                if posting.meta and posting.meta.get('closing', False):
                    # Remove the metadata.
                    meta = posting.meta.copy()
                    del meta['closing']
                    posting = posting._replace(meta=meta)
                    entry.postings[i] = posting

                    # Insert a balance.
                    date = entry.date + datetime.timedelta(days=1)
                    balance = data.Balance(data.new_metadata("<check_closing>", 0),
                                           date, posting.account,
                                           amount.Amount(ZERO, posting.units.currency),
                                           None, None)
                    new_entries.append(balance)
        new_entries.append(entry)
    return new_entries, []
Exemple #7
0
    def create_sort_data(self):
        account = 'Assets:Bank:Checking'
        date1 = date(2014, 1, 15)
        date2 = date(2014, 1, 18)
        date3 = date(2014, 1, 20)
        entries = [
            data.Transaction(data.new_metadata(".", 1100), date3, FLAG, None,
                             "Next day", None, None, []),
            data.Close(data.new_metadata(".", 1000), date2, account),
            data.Balance(data.new_metadata(".", 1001), date2, account,
                         A('200.00 USD"'), None, None),
            data.Open(data.new_metadata(".", 1002), date2, account, 'USD',
                      None),
            data.Transaction(data.new_metadata(".", 1009), date2, FLAG, None,
                             "Transaction 2", None, None, []),
            data.Transaction(data.new_metadata(".", 1008), date2, FLAG, None,
                             "Transaction 1", None, None, []),
            data.Transaction(data.new_metadata(".", 900), date1, FLAG, None,
                             "Previous day", None, None, []),
        ]

        for entry in entries:
            if isinstance(entry, data.Transaction):
                data.create_simple_posting(entry, 'Assets:Bank:Checking',
                                           '123.45', 'USD')

        return entries
Exemple #8
0
 def balance_assertion(transaction, opening=False, closing=False):
     lineno = transaction[0]
     line = transaction[1]
     balance = _format_number_de(line['Saldo'])
     if opening:
         # calculate balance before the first transaction
         # Currencies must match for subtraction
         if line['Währung_1'] != line['Währung_2']:
             warnings.warn(
                 f"{file_.name}:{lineno} "
                 "opening balance can not be generated "
                 "due to currency mismatch: "
                 f"{line['Währung_1']} <> {line['Währung_2']}")
             return []
         balance -= _format_number_de(line['Betrag'])
         balancedate = self._date_from
     if closing:
         # balance after the last transaction:
         # next day's opening balance
         balancedate = self._date_to + timedelta(days=1)
     return [
         data.Balance(
             data.new_metadata(file_.name, lineno),
             balancedate,
             self.account,
             Amount(balance, line['Währung_1']),
             None,
             None,
         )
     ]
Exemple #9
0
 def Balances(self,cr):
     # generate Balance statements from IBKR Cash reports 
     # balances 
     crTransactions = []
     for idx, row in cr.iterrows():
         currency=row['currency']
         if currency == 'BASE_SUMMARY':
             continue # this is a summary balance that is not needed for beancount
         amount_=amount.Amount(row['endingCash'].__round__(2),currency)
         
         # make the postings. two for deposits
         postings=[data.Posting(self.depositAccount,
                                 -amount_, None, None, None, None),
                     data.Posting(self.getLiquidityAccount(currency),
                                 amount_,None, None, None, None)
                     ]
         meta=data.new_metadata('balance',0)
         
         crTransactions.append(data.Balance(
                         meta,
                         row['toDate'] + timedelta(days=1), # see tariochtools EC imp.
                         self.getLiquidityAccount(currency),
                         amount_,
                         None,
                         None))
     return crTransactions
Exemple #10
0
def deserialise(json_entry):
    """Parse JSON to a Beancount entry.

    Args:
        json_entry: The entry.

    Raises:
        KeyError: if one of the required entry fields is missing.
        FavaAPIException: if the type of the given entry is not supported.
    """
    if json_entry['type'] == 'Transaction':
        date = util.date.parse_date(json_entry['date'])[0]
        narration, tags, links = extract_tags_links(json_entry['narration'])
        postings = [deserialise_posting(pos) for pos in json_entry['postings']]
        return data.Transaction(json_entry['meta'], date, json_entry['flag'],
                                json_entry['payee'], narration, tags, links,
                                postings)
    if json_entry['type'] == 'Balance':
        date = util.date.parse_date(json_entry['date'])[0]
        number = parse_number(json_entry['number'])
        amount = Amount(number, json_entry['currency'])

        return data.Balance(json_entry['meta'], date, json_entry['account'],
                            amount, None, None)
    if json_entry['type'] == 'Note':
        date = util.date.parse_date(json_entry['date'])[0]
        comment = json_entry['comment'].replace('"', '')
        return data.Note(json_entry['meta'], date, json_entry['account'],
                         comment)
    raise FavaAPIException('Unsupported entry type.')
Exemple #11
0
 def create_balance_entry(self, filename, date, balance):
     # Balance directives will be sorted in front of transactions, so there is no need
     # to have a line number to break ties.
     meta = data.new_metadata(filename, 0)
     balance_entry = data.Balance(meta, date, self.account,
                                  amount.Amount(balance, self.currency),
                                  None, None)
     return balance_entry
Exemple #12
0
    def extract(self, file, existing_entries):
        entries = []
        has_balance = False

        with StringIO(file.contents()) as csvfile:
            reader = csv.DictReader(csvfile, [
                'Date', 'Reference', 'PaidOut', 'PaidIn', 'ExchangeOut',
                'ExchangeIn', 'Balance', 'Category'
            ],
                                    delimiter=';',
                                    skipinitialspace=True)
            next(reader)
            for row in reader:
                metakv = {
                    'category': row['Category'].strip(),
                }
                exchangeIn = row['ExchangeIn'].strip()
                exchangeOut = row['ExchangeOut'].strip()
                if exchangeIn and exchangeOut:
                    metakv['originalIn'] = exchangeIn
                    metakv['originalOut'] = exchangeOut
                elif exchangeIn:
                    metakv['original'] = exchangeIn
                elif exchangeOut:
                    metakv['original'] = exchangeOut

                book_date = parse(row['Date'].strip()).date()

                try:
                    credit = D(row['PaidIn'].replace('\'', '').strip())
                    debit = D(row['PaidOut'].replace('\'', '').strip())
                    bal = D(row['Balance'].replace('\'', '').strip())
                    amt = amount.Amount(credit - debit, self.currency)
                    balance = amount.Amount(bal, self.currency)
                except Exception as e:
                    logging.warning(e)
                    continue

                meta = data.new_metadata(file.name, 0, metakv)
                entry = data.Transaction(
                    meta, book_date, '*', '', row['Reference'].strip(),
                    data.EMPTY_SET, data.EMPTY_SET, [
                        data.Posting(self.account, amt, None, None, None,
                                     None),
                    ])
                entries.append(entry)

                # only add balance after the top (newest) transaction
                if not has_balance:
                    book_date = book_date + timedelta(days=1)
                    entry = data.Balance(meta, book_date, self.account,
                                         balance, None, None)
                    entries.append(entry)
                    has_balance = True

        return entries
Exemple #13
0
def create_balance(filename, date, account, amount_):
    balance = data.Balance(
        data.new_metadata(filename, ""),  # metadata
        date,  # date
        account,  # account
        amount_,  # amount
        None,  # tolerance
        None,  # diff_amount
    )
    return balance
Exemple #14
0
 def createBalanceEntry(self, file, date, amt):
     meta = data.new_metadata(file.name, 0)
     return data.Balance(
         meta,
         parse(date.strip(), dayfirst=True).date() + timedelta(days=1),
         self.account,
         amount.Amount(D(amt), 'CHF'),
         None,
         None,
     )
 def extract_balance(self, file, counter):
     entries = []
     meta = data.new_metadata(file.name, next(counter))
     for bal in self.get_balance_statement():
         if bal:
             balance_entry = data.Balance(meta, bal.date, self.config['main_account'],
                                          amount.Amount(bal.amount, self.currency),
                                          None, None)
             entries.append(balance_entry)
     return entries
def CreateBalance(api, accountId) -> data.Balance:
    """Create a Balance directive for the account."""
    acc = api.GetAccount(accountId=accountId)
    balances = acc['securitiesAccount']['currentBalances']
    mmfund = balances['moneyMarketFund']
    shorts = balances['shortBalance']
    amt = mmfund + shorts
    fileloc = data.new_metadata('<ameritrade>', 0)
    date = datetime.date.today()
    return data.Balance(fileloc, date, config['asset_cash'],
                        Amount(D(amt).quantize(Q), USD), None, None)
Exemple #17
0
def balance(file, account_name, currency, statement, ledger):
    if len(ledger) == 0:
        return None
    # Use the last transaction date as the balance assertion date
    # (because the pending transactions will post in-between)
    date = ledger[-1].date
    # The Balance assertion occurs at the beginning of the date, so move
    # it to the following day.
    date += timedelta(days=1)
    units = Amount(statement.balance, currency)
    ref = data.new_metadata(file.name, 0)
    return data.Balance(ref, date, account_name, units, None, None)
Exemple #18
0
 def extract_balance(self, file, counter):
     # date = self.ofx_account.statement.balance_date
     date = max(ot.tradeDate if hasattr(ot, 'tradeDate') else ot.date
                for ot in self.get_transactions()).date()
     # balance assertions are evaluated at the beginning of the date, so move it to the following day
     date += datetime.timedelta(days=1)
     meta = data.new_metadata(file.name, next(counter))
     balance_entry = data.Balance(
         meta, date, self.config['main_account'],
         amount.Amount(self.ofx_account.statement.balance, self.currency),
         None, None)
     return [balance_entry]
Exemple #19
0
def extract(soup, filename, acctid_regexp, account, flag, balance_type):
    """Extract transactions from an OFX file.

    Args:
      soup: A BeautifulSoup root node.
      acctid_regexp: A regular expression string matching the account we're interested in.
      account: An account string onto which to post the amounts found in the file.
      flag: A single-character string.
      balance_type: An enum of type BalanceType.
    Returns:
      A sorted list of entries.
    """
    new_entries = []
    counter = itertools.count()
    for acctid, currency, transactions, balance in find_statement_transactions(
            soup):
        if not re.match(acctid_regexp, acctid):
            continue

        # Create Transaction directives.
        stmt_entries = []
        for stmttrn in transactions:
            entry = build_transaction(stmttrn, flag, account, currency)
            entry = entry._replace(
                meta=data.new_metadata(filename, next(counter)))
            stmt_entries.append(entry)
        stmt_entries = data.sorted(stmt_entries)
        new_entries.extend(stmt_entries)

        # Create a Balance directive.
        if balance and balance_type is not BalanceType.NONE:
            date, number = balance
            if balance_type is BalanceType.LAST and stmt_entries:
                date = stmt_entries[-1].date

            # The Balance assertion occurs at the beginning of the date, so move
            # it to the following day.
            date += datetime.timedelta(days=1)

            meta = data.new_metadata(filename, next(counter))
            balance_entry = data.Balance(meta, date, account,
                                         amount.Amount(number, currency), None,
                                         None)
            new_entries.append(balance_entry)

    return data.sorted(new_entries)
Exemple #20
0
def json_to_entry(json_entry, valid_accounts):
    """Parse JSON to a Beancount entry."""
    # pylint: disable=not-callable
    date = util.date.parse_date(json_entry['date'])[0]
    if json_entry['type'] == 'transaction':
        narration, tags, links = extract_tags_links(json_entry['narration'])
        txn = data.Transaction(json_entry['metadata'], date,
                               json_entry['flag'], json_entry['payee'],
                               narration, tags, links, [])

        if not json_entry.get('postings'):
            raise FavaAPIException('Transaction contains no postings.')

        for posting in json_entry['postings']:
            if posting['account'] not in valid_accounts:
                raise FavaAPIException('Unknown account: {}.'.format(
                    posting['account']))
            data.create_simple_posting(txn, posting['account'],
                                       posting.get('number') or None,
                                       posting.get('currency'))

        return txn
    elif json_entry['type'] == 'balance':
        if json_entry['account'] not in valid_accounts:
            raise FavaAPIException('Unknown account: {}.'.format(
                json_entry['account']))
        number = D(json_entry['number'])
        amount = Amount(number, json_entry.get('currency'))

        return data.Balance(json_entry['metadata'], date,
                            json_entry['account'], amount, None, None)
    elif json_entry['type'] == 'note':
        if json_entry['account'] not in valid_accounts:
            raise FavaAPIException('Unknown account: {}.'.format(
                json_entry['account']))

        if '"' in json_entry['comment']:
            raise FavaAPIException('Note contains double-quotes (")')

        return data.Note(json_entry['metadata'], date, json_entry['account'],
                         json_entry['comment'])
    else:
        raise FavaAPIException('Unsupported entry type.')
Exemple #21
0
 def get_member(self, id, name, date, balance):
     #name = member_account(name)
     name = self.accounts_by_id[id]
     if id in self.accounts_by_id and self.accounts_by_id[id] != name:
         # Generate rename
         txn = bcdata.Transaction(meta={},
                                  date=date,
                                  flag="txn",
                                  payee="",
                                  narration="Rename %(from)s to %(to)s" % {
                                      "from": self.accounts_by_id[id],
                                      "to": name,
                                  },
                                  tags=set("rename"),
                                  links=set(),
                                  postings=[])
         bcdata.create_simple_posting(txn, self.accounts_by_id[id], balance,
                                      "EUR")
         bcdata.create_simple_posting(txn, name, -balance, "EUR")
         self.entries.append(txn)
         if self.accounts_by_id[id] not in DONT_CLOSE:
             self.entries.append(
                 bcdata.Close(
                     meta={},
                     date=date + datetime.timedelta(days=1),
                     account=self.accounts_by_id[id],
                 ))
         # Disable the balance assertion for the next day
         self.last_assertion[name] = date
         self.initial_balances[name] = None
         self.accounts_by_id[id] = name
     elif name not in self.initial_balances:
         self.initial_balances[name] = balance
         self.last_assertion[name] = date
         self.accounts_by_id[id] = name
     elif self.last_assertion.get(
             name, None) != date and name != "Assets:Cash:Bar":
         self.entries.append(
             bcdata.Balance({"iline": str(self.line)}, date, name,
                            bcdata.Amount(-balance, "EUR"), None, None))
         self.last_assertion[name] = date
     return name
Exemple #22
0
    def extract(self, file, existing_entries):
        csvfile = open(file=file.name, encoding='windows_1252')
        reader = csv.reader(csvfile, delimiter=';')
        meta = data.new_metadata(file.name, 0)
        entries = []

        for row in reader:

            try:
                book_date, text, credit, debit, val_date, balance = tuple(row)
                book_date = datetime.strptime(book_date, '%Y-%m-%d').date()
                if credit:
                    amount = data.Amount(Decimal(credit), self.currency)
                elif debit:
                    amount = data.Amount(Decimal(debit), self.currency)
                else:
                    amount = None
                if balance:
                    balance = data.Amount(Decimal(balance), self.currency)
                else:
                    balance = None
            except Exception as e:
                logging.debug(e)
            else:
                logging.debug((book_date, text, amount, val_date, balance))
                posting = data.Posting(self.account, amount, None, None, None,
                                       None)
                entry = data.Transaction(meta, book_date, '*', '', text,
                                         data.EMPTY_SET, data.EMPTY_SET,
                                         [posting])
                entries.append(entry)
                # only add balance on SOM
                book_date = book_date + timedelta(days=1)
                if balance and book_date.day == 1:
                    entry = data.Balance(meta, book_date, self.account,
                                         balance, None, None)
                    entries.append(entry)

        csvfile.close()
        entries = data.sorted(entries)
        return entries
Exemple #23
0
    def extract_prices(self,
                       statement: FlexStatement,
                       existing_entries: list = None):
        """
        IBFlex XML Files can contain an object called 'OpenPositions',
        this is very useful because it lets us create

        - Balance assertions
        - Price entries from the Mark
        """
        result = []
        for position in statement.OpenPositions:
            price = position.markPrice
            safe_symbol = self.clean_symbol(position.symbol)
            # Dates are 12 Midnight, let's make it the next day
            date = statement.toDate + timedelta(days=1)
            result.append(
                # TODO De-Duplicate
                data.Price(currency=safe_symbol,
                           amount=data.Amount(price, "USD"),
                           date=date,
                           meta={
                               'lineno': 0,
                               'filename': '',
                           }))
            account = self.account_for_symbol(statement, position.symbol)
            result.append(
                data.Balance(
                    account=account,
                    amount=data.Amount(position.position * position.multiplier,
                                       safe_symbol),
                    date=statement.toDate + timedelta(days=1),
                    meta={
                        'lineno': 0,
                        'filename': ''
                    },
                    tolerance=0.5,
                    diff_amount=0,
                ))
        return result
Exemple #24
0
def deserialise(json_entry):
    """Parse JSON to a Beancount entry.

    Args:
        json_entry: The entry.

    Raises:
        KeyError: if one of the required entry fields is missing.
        FavaAPIException: if the type of the given entry is not supported.
    """
    if json_entry["type"] == "Transaction":
        date = util.date.parse_date(json_entry["date"])[0]
        narration, tags, links = extract_tags_links(json_entry["narration"])
        postings = [deserialise_posting(pos) for pos in json_entry["postings"]]
        return data.Transaction(
            json_entry["meta"],
            date,
            json_entry.get("flag", ""),
            json_entry.get("payee", ""),
            narration,
            tags,
            links,
            postings,
        )
    if json_entry["type"] == "Balance":
        date = util.date.parse_date(json_entry["date"])[0]
        raw_amount = json_entry["amount"]
        amount = Amount(D(str(raw_amount["number"])), raw_amount["currency"])

        return data.Balance(
            json_entry["meta"], date, json_entry["account"], amount, None, None
        )
    if json_entry["type"] == "Note":
        date = util.date.parse_date(json_entry["date"])[0]
        comment = json_entry["comment"].replace('"', "")
        return data.Note(
            json_entry["meta"], date, json_entry["account"], comment
        )
    raise FavaAPIException("Unsupported entry type.")
Exemple #25
0
def deserialise(json_entry):
    """Parse JSON to a Beancount entry.

    Args:
        json_entry: The entry.

    Raises:
        KeyError: if one of the required entry fields is missing.
        FavaAPIException: if the type of the given entry is not supported.
    """
    # pylint: disable=not-callable
    if json_entry['type'] == 'Transaction':
        date = util.date.parse_date(json_entry['date'])[0]
        narration, tags, links = extract_tags_links(json_entry['narration'])
        txn = data.Transaction(json_entry['meta'], date, json_entry['flag'],
                               json_entry['payee'], narration, tags, links, [])

        for posting in json_entry['postings']:
            data.create_simple_posting(txn, posting['account'],
                                       parse_number(posting.get('number')),
                                       posting.get('currency'))

        return txn
    elif json_entry['type'] == 'Balance':
        date = util.date.parse_date(json_entry['date'])[0]
        number = parse_number(json_entry['number'])
        amount = Amount(number, json_entry['currency'])

        return data.Balance(json_entry['meta'], date, json_entry['account'],
                            amount, None, None)
    elif json_entry['type'] == 'Note':
        date = util.date.parse_date(json_entry['date'])[0]
        comment = json_entry['comment'].replace('"', '')
        return data.Note(json_entry['meta'], date, json_entry['account'],
                         comment)
    else:
        raise FavaAPIException('Unsupported entry type.')
Exemple #26
0
    def extract(self, file_):
        entries = []
        line_index = 0
        closing_balance_index = -1

        with open(file_.name, encoding=self.file_encoding) as fd:
            # Header
            line = fd.readline().strip()
            line_index += 1

            if not self.is_valid_header(line):
                raise InvalidFormatError()

            # Empty line
            line = fd.readline().strip()
            line_index += 1

            if line:
                raise InvalidFormatError()

            # Meta
            expected_keys = set(['Von:', 'Bis:', 'Saldo:', 'Datum:'])

            lines = [fd.readline().strip() for _ in range(len(expected_keys))]

            reader = csv.reader(
                lines, delimiter=';', quoting=csv.QUOTE_MINIMAL, quotechar='"'
            )

            for line in reader:
                key, value, _ = line
                line_index += 1

                if key.startswith('Von'):
                    self._date_from = datetime.strptime(
                        value, '%d.%m.%Y'
                    ).date()
                elif key.startswith('Bis'):
                    self._date_to = datetime.strptime(value, '%d.%m.%Y').date()
                elif key.startswith('Saldo'):
                    self._balance_amount = Amount(
                        Decimal(value.rstrip(' EUR')), self.currency
                    )
                    closing_balance_index = line_index
                elif key.startswith('Datum'):
                    self._balance_date = datetime.strptime(
                        value, '%d.%m.%Y'
                    ).date() + timedelta(days=1)

                expected_keys.remove(key)

            if expected_keys:
                raise ValueError()

            # Another empty line
            line = fd.readline().strip()
            line_index += 1

            if line:
                raise InvalidFormatError()

            # Data entries
            reader = csv.DictReader(
                fd, delimiter=';', quoting=csv.QUOTE_MINIMAL, quotechar='"'
            )

            for index, line in enumerate(reader):
                meta = data.new_metadata(file_.name, index)

                amount = Amount(
                    fmt_number_de(line['Betrag (EUR)']), self.currency
                )

                date = datetime.strptime(line['Belegdatum'], '%d.%m.%Y').date()

                description = line['Beschreibung']

                postings = [
                    data.Posting(self.account, amount, None, None, None, None)
                ]

                entries.append(
                    data.Transaction(
                        meta,
                        date,
                        self.FLAG,
                        None,
                        description,
                        data.EMPTY_SET,
                        data.EMPTY_SET,
                        postings,
                    )
                )

            # Closing Balance
            meta = data.new_metadata(file_.name, closing_balance_index)
            entries.append(
                data.Balance(
                    meta,
                    self._balance_date,
                    self.account,
                    self._balance_amount,
                    None,
                    None,
                )
            )

        return entries
Exemple #27
0
    def extract(self, file):
        entries = []

        # Normalize the configuration to fetch by index.
        iconfig, has_header = normalize_config(self.config, file.head())

        # Skip header, if one was detected.
        reader = iter(csv.reader(open(file.name)))
        if has_header:
            next(reader)

        def get(row, ftype):
            return row[iconfig[ftype]] if ftype in iconfig else None

        # Parse all the transactions.
        first_row = last_row = None
        for index, row in enumerate(reader, 1):
            if not row:
                continue

            # If debugging, print out the rows.
            if self.debug: print(row)

            if first_row is None:
                first_row = row
            last_row = row

            # Extract the data we need from the row, based on the configuration.
            date = get(row, Col.DATE)
            txn_date = get(row, Col.TXN_DATE)

            payee = get(row, Col.PAYEE)
            fields = filter(None, [
                get(row, field)
                for field in (Col.NARRATION1, Col.NARRATION2, Col.NARRATION3)
            ])
            narration = ' -- '.join(fields)

            tag = get(row, Col.TAG)
            tags = {tag} if tag is not None else data.EMPTY_SET

            # Create a transaction and add it to the list of new entries.
            meta = data.new_metadata(file.name, index)
            if txn_date is not None:
                meta['txndate'] = parse_date_liberally(txn_date)
            date = parse_date_liberally(date)
            txn = data.Transaction(meta, date, self.FLAG, payee, narration,
                                   tags, data.EMPTY_SET, [])
            entries.append(txn)

            amount_debit, amount_credit = get_amounts(iconfig, row)
            for amount in [amount_debit, amount_credit]:
                if amount is None:
                    continue
                units = Amount(amount, self.currency)
                txn.postings.append(
                    data.Posting(self.account, units, None, None, None, None))

        # Figure out if the file is in ascending or descending order.
        first_date = parse_date_liberally(get(first_row, Col.DATE))
        last_date = parse_date_liberally(get(last_row, Col.DATE))
        is_ascending = first_date < last_date

        # Parse the final balance.
        if Col.BALANCE in iconfig:
            # Choose between the first or the last row based on the date.
            row = last_row if is_ascending else first_row
            date = parse_date_liberally(get(
                row, Col.DATE)) + datetime.timedelta(days=1)
            balance = D(get(row, Col.BALANCE))
            meta = data.new_metadata(file.name, index)
            entries.append(
                data.Balance(meta, date, self.account,
                             Amount(balance, self.currency), None, None))

        return entries
Exemple #28
0
    def extract(self, f, existing_entries=None):
        entries = []

        row = None
        row_date = None

        with open_file(f) as fd:
            rd = csv.reader(fd, dialect="ccm")

            header = True
            line_index = 0

            for row in rd:
                # Check header
                if header:
                    if set(row) != set(FIELDS):
                        raise InvalidFormatError()
                    header = False

                    line_index += 1

                    continue

                if len(row) != 6:
                    continue

                # Extract data

                row_date = datetime.strptime(row[0], "%d/%m/%Y")
                label = row[4]

                txn_amount = row[2]
                if txn_amount == '':
                    txn_amount = row[3]
                txn_amount = parse_amount(txn_amount)

                # Prepare the transaction

                meta = data.new_metadata(f.name, line_index)

                txn = data.Transaction(
                    meta=meta,
                    date=row_date.date(),
                    flag=flags.FLAG_OKAY,
                    payee="",
                    narration=label,
                    tags=set(),
                    links=set(),
                    postings=[],
                )

                # Create the postings.

                first_posting = make_posting(self.checking_account, txn_amount)
                txn.postings.append(first_posting)

                # Done

                entries.append(txn)

                line_index += 1

        if line_index > 0:
            balance_check = data.Balance(
                meta=data.new_metadata(f.name, line_index + 1),
                date=row_date.date() + timedelta(days=1),
                account=self.checking_account,
                amount=parse_amount(row[5]),
                diff_amount=None,
                tolerance=None,
            )
            entries.append(balance_check)

        return entries
Exemple #29
0
    def extract(self, file_):
        entries = []
        line_index = 0
        closing_balance_index = -1

        with open(file_.name, encoding=self.file_encoding) as fd:
            # Header
            line = fd.readline().strip()
            line_index += 1

            if not self._expected_header_regex.match(line):
                raise InvalidFormatError()

            # (sometimes) Empty line
            line = fd.readline().strip()
            line_index += 1

            # Meta
            lines = [fd.readline().strip() for _ in range(3)]

            reader = csv.reader(lines,
                                delimiter=';',
                                quoting=csv.QUOTE_MINIMAL,
                                quotechar='"')

            for line in reader:
                key = line[0]
                value = line[1]
                line_index += 1

                if key.startswith('Von'):
                    self._date_from = datetime.strptime(value,
                                                        '%d.%m.%Y').date()
                elif key.startswith('Bis'):
                    self._date_to = datetime.strptime(value, '%d.%m.%Y').date()
                elif key.startswith('Kontostand vom'):
                    # Beancount expects the balance amount to be from the
                    # beginning of the day, while the Tagessaldo entries in
                    # the DKB exports seem to be from the end of the day.
                    # So when setting the balance date, we add a timedelta
                    # of 1 day to the original value to make the balance
                    # assertions work.

                    self._balance_amount = Amount(
                        fmt_number_de(value.rstrip(' EUR')), self.currency)
                    self._balance_date = datetime.strptime(
                        key.lstrip('Kontostand vom ').rstrip(':'),
                        '%d.%m.%Y').date() + timedelta(days=1)
                    closing_balance_index = line_index

            # Another (sometimes) empty line
            line = fd.readline().strip()
            line_index += 1

            # Data entries
            reader = csv.DictReader(fd,
                                    delimiter=';',
                                    quoting=csv.QUOTE_MINIMAL,
                                    quotechar='"')

            for line in reader:
                meta = data.new_metadata(file_.name, line_index)

                amount = None
                if line['Betrag (EUR)']:
                    amount = Amount(fmt_number_de(line['Betrag (EUR)']),
                                    self.currency)
                date = datetime.strptime(line['Buchungstag'],
                                         '%d.%m.%Y').date()

                if line['Verwendungszweck'] == 'Tagessaldo':
                    if amount:
                        entries.append(
                            data.Balance(
                                meta,
                                date + timedelta(days=1),
                                self.account,
                                amount,
                                None,
                                None,
                            ))
                else:
                    description = '{} {}'.format(line['Buchungstext'],
                                                 line['Verwendungszweck'])

                    postings = [
                        data.Posting(self.account, amount, None, None, None,
                                     None)
                    ]

                    entries.append(
                        data.Transaction(
                            meta,
                            date,
                            self.FLAG,
                            line['Auftraggeber / Begünstigter'],
                            description,
                            data.EMPTY_SET,
                            data.EMPTY_SET,
                            postings,
                        ))

                line_index += 1

            # Closing Balance
            meta = data.new_metadata(file_.name, closing_balance_index)
            entries.append(
                data.Balance(
                    meta,
                    self._balance_date,
                    self.account,
                    self._balance_amount,
                    None,
                    None,
                ))

        return entries
Exemple #30
0
def table_to_directives(table: petl.Table,
                        currency: str = 'USD') -> data.Entries:
    """Convert a petl table to Beancount directives.

    This is intended as a convenience for many simple CSV importers. Your CSV
    code uses petl to normalize the contents of an imported file to a table, and
    this routine is called to actually translate that into directives.

    Required columns of the input 'table' are:
      date: A datetime.date instance, for the transaction.
      account: An account string, for the posting.
      amount: A Decimal instance, the number you want on the posting.
    Optional columns are:
      payee: A string, for the transaction's payee.
      narration: A string, for the transaction's narration.
      balance: A Decimal, the balance in the account *after* the given transaction.
      other_account: An account string, for the remainder of the transaction.
    """
    # Ensure the table is sorted in order to produce the final balance.
    assert table.issorted('date')
    assert set(table.fieldnames()) >= {'date', 'account', 'amount'}

    columns = table.fieldnames()
    metas = []
    for column in columns:
        match = re.match("meta:(.*)", column)
        if match:
            metas.append((column, match.group(1)))

    # Create transactions.
    entries = []
    for index, rec in enumerate(table.records()):
        meta = data.new_metadata(f"<{__file__}>".format, index)
        units = amount.Amount(rec.amount, currency)
        tags, links = set(), set()
        txn = data.Transaction(
            meta, rec.date, flags.FLAG_OKAY, getattr(rec, 'payee', None),
            getattr(rec, 'narration', ""), tags, links,
            [data.Posting(rec.account, units, None, None, None, None)])
        if hasattr(rec, 'other_account') and rec.other_account:
            txn.postings.append(
                data.Posting(rec.other_account, None, None, None, None, None))
        link = getattr(rec, 'link', None)
        if link:
            links.add(link)
        tag = getattr(rec, 'tag', None)
        if tag:
            tags.add(tag)

        for column, key in metas:
            value = getattr(rec, column, None)
            if value:
                meta[key] = value
        entries.append(txn)

    if 'balance' in columns:
        # Insert a balance with the final value.
        meta = data.new_metadata(f"<{__file__}>", index + 1)
        balance_date = rec.date + datetime.timedelta(days=1)
        entries.append(
            data.Balance(meta, balance_date, rec.account,
                         amount.Amount(rec.balance, currency), None, None))

    return entries