def parse_record(self, line):
        if line[0] == "Kontonummer":
            # it's the table header
            return None

        if len(line) < 3:
            """e.g.: ['# 1 vorgemerkte Umsätze nicht angezeigt']"""
            return None
        if not line[2]:
            return None

        if self.statement.account_id is None:
            self.statement.account_id = line[0]

        sl = StatementLine()
        sl.id = line[1]
        sl.date = self.parse_datetime(line[2])
        sl.date_avail = self.parse_datetime(line[3])
        sl.amount = self.parse_float(line[4])
        sl.trntype = TMAPPINGS.get(line[5],
                                   'DEBIT' if sl.amount < 0 else 'CREDIT')
        sl.payee = line[7][:32]
        sl.memo = "%s: %s" % (line[6],
                              " ".join(x for x in line[13:31] if len(x) > 0))

        if len(line[8]) > 0 and len(line[9]) > 0:
            # additional bank information is present
            splitted = self.parse_iban(line[8])
            if splitted:
                sl.bank_account_to = BankAccount(**splitted)
            else:
                sl.bank_account_to = BankAccount(line[9], line[8])

        return sl
    def parse_record(self, line):
        # FIXME: add header validation
        if self.cur_record < 2:
            return None
        if len(line) < 3:
            """e.g.: ['# 1 vorgemerkte Umsätze nicht angezeigt']"""
            return None
        if not line[2]:
            return None
        sl = StatementLine()
        sl.id = line[1]
        sl.date = self.parse_datetime(line[2])
        sl.amount = self.parse_float(line[4])
        sl.trntype = 'DEBIT' if sl.amount < 0 else 'CREDIT'
        sl.payee = line[7]
        # check for special transactions
        if line[6] == "Entgeltabschluss":
            sl.memo = "%s: %s %s" % (line[6], line[13], line[14])
        elif line[6] == "Wertpapiere" or line[7] == "KREDITKARTENABRECHNUNG":
            sl.memo = "(%s/%s): %s" % (line[8], line[9], " ".join(line[15:]).strip())
        elif not line[8] and not line[9]:
            # empty transaction
            print("empty", line)
            return None
        else:
            sl.memo = "(%s/%s): %s" % (line[8], line[9], " ".join(e for e in line[13:] if e).strip())

        return sl
Example #3
0
    def parse_record(self, rec):
        MultilineRecRE = re.compile(r"\n\s+")
        SplitItemRE = re.compile(r":\s+")
        # join together multiline items
        rec = MultilineRecRE.sub(" ", rec)
        res_iter = (SplitItemRE.split(line) for line in rec.split("\n"))
        res_dict = dict((item[0].strip(), item[1].strip())
            for item in res_iter)
        res_key_set = set(res_dict.keys())
        # Insurance against unknown keys in the statement
        assert res_key_set <= self._keys, \
            "Unknown keys in the transaction: %s" % (res_key_set - self._keys)

        # no idea, how to make this unique and monotonically increasing
        trans_ID = res_dict['datum zaúčtování'] + res_dict['částka'] + \
            res_dict['variabilní symbol'] + res_dict['protiúčet']
        date_str = res_dict['datum zaúčtování'].split('.')
        stat_line = StatementLine(id=trans_ID,
            date=datetime.date(int(date_str[2]), int(date_str[1]),
                int(date_str[0])),
            memo="%s\nozn. operace: %s" % (res_dict['poznámka'],
                res_dict['označení operace']),
            amount=float(res_dict['částka']))
        stat_line.payee = res_dict['název protiúčtu']
        # According to OFX spec this could be "check (or other reference) no."
        # I don't see any requirement on monotonicity or uniqueness, but
        # I wonder how GnuCash understands that
        stat_line.check_no = res_dict['variabilní symbol']
        return stat_line
 def testAllFilled(self):
     """All of the fields used in the hash have a value"""
     sline = StatementLine()
     sline.date = date(2015, 10, 2)
     sline.memo = "Test statement line"
     sline.amount = 123.45
     self.assertEqual(self.all_hash, generate_stable_transaction_id(sline))
    def parse_record(self, line):
        san_line = {}
        for lkey, lval in line.items():
            san_line[lkey] = re.sub("\$", "", lval)

        stmt_line = StatementLine()
        for field, col in self.mappings.items():
            rawvalue = san_line[col]
            try:
                value = self.parse_value(rawvalue, field)
            except ValueError as e:
                # Invalid data line; skip it
                warning("Error parsing value '{}' on line '{}': {}".format(rawvalue, san_line, e))
                return None
            setattr(stmt_line, field, value)

        if self.statement.filter_zeros and is_zero(stmt_line.amount):
            return None

        try:
            stmt_line.end_balance = float(san_line['Ending Balance'])
        except ValueError:
            # Usually indicates a pending transaction
            return None

        # generate transaction id out of available data
        stmt_line.id = generate_stable_transaction_id(stmt_line)
        return stmt_line
Example #6
0
    def parse_record(self, row):
        """Parse given table row and return StatementLine object
        """

        # if we're still in header, no transactions yet, just collect account data
        if self.in_header:
            m = match(
                r"""(Prometi\ za\ razdoblje\ od\ (?P<start>[0-9.]+)\ do\ (?P<end>[0-9.]+))
                         |(Račun:\ (?P<acct>\w+))   # account           ^- start&end dates
                         |(Valuta:\ (?P<curr>\w+))  # currency
                         |(?P<eoh>Datum)            # end of headers""",
                row[0].value, VERBOSE)
            if m:
                # start/end
                if m['start']:
                    self.statement.start_date = datetime.strptime(
                        m['start'], self.header_date_format)
                    self.statement.end_date = datetime.strptime(
                        m['end'], self.header_date_format)
                # account number
                if m['acct']:
                    self.statement.account_id = m['acct']
                # currency
                elif m['curr']:
                    self.statement.currency = m['curr']
                # end of headers
                elif m['eoh']:
                    self.in_header = False
            self.row_nr += 1
            return None

        # main body of transactions after headers
        stmt_line = StatementLine(amount=0)

        for field, col in self.mappings.items():
            if col >= len(row):
                raise ValueError(
                    "Cannot find column %s in a row of %s items " %
                    (col, len(row)))
            cell = row[col]
            value = self.parse_value(cell)
            # calculate debits and credits to amount
            if field == 'debit' and value != 0:
                stmt_line.amount += value
                stmt_line.trntype = 'DEBIT'
            elif field == 'credit' and value != 0:
                stmt_line.amount -= value
                stmt_line.trntype = 'CREDIT'
            else:
                setattr(stmt_line, field, value)

        # apply generated transaction id
        stmt_line.id = self.gen_id(stmt_line)

        # overwrite end balance with current balance
        self.statement.end_balance = stmt_line.balance
        # next row
        self.row_nr += 1
        return stmt_line
    def parse_record(self, record):
        P = self.P

        sl = StatementLine()

        sl.date = self.parse_datetime(record.findall(P('ValDt', 'Dt'))[0].text)
        sl.amount = float(record.findall(P('Amt'))[0].text)
        sl.trntype = 'DEBIT' if (record.findall(P('CdtDbtInd'))[0].text
                                 == "DBIT") else 'CREDIT'
        if sl.trntype == 'DEBIT' and sl.amount > 0:
            sl.amount = -sl.amount
        sl.payee = record.findall(
            P('NtryDtls', 'TxDtls', 'RltdPties', 'Cdtr', 'Nm'))[0].text
        sl.memo = ' '.join([
            e.text
            for e in record.findall(P('NtryDtls', 'TxDtls', 'RmtInf', 'Ustrd'))
        ])

        # generate unique id
        h = hashlib.sha256()
        h.update(str(sl.date).encode('utf-8'))
        h.update(str(sl.amount).encode('utf-8'))
        h.update(str(sl.trntype).encode('utf-8'))
        h.update(str(sl.payee).encode('utf-8'))
        h.update(str(sl.memo).encode('utf-8'))

        sl.id = h.hexdigest()

        return sl
Example #8
0
    def parse_record(self, df_row):
        """Parse given transaction line and return StatementLine object
        """
        stmt_line = StatementLine()        

        # date field
        stmt_line.date = self.xls_date(
            self.df['Data Contabile'][self.df_row_idx])

        # amount field
        stmt_line.amount = self.df['Importo'][self.df_row_idx]
        
        # transaction type field
        if(stmt_line.amount < 0):
            stmt_line.trntype = "DEBIT"
        else:
            stmt_line.trntype = "CREDIT"

        # memo field        
        stmt_line.memo = self.df['Causale / Descrizione'][self.df_row_idx]
        if(pd.isnull(stmt_line.memo)):
            stmt_line.memo = ''

        # id field
        stmt_line.id = generate_transaction_id(stmt_line)
        #print(str(stmt_line))
        return stmt_line
Example #9
0
 def to_statement_line(self, raw_records):
     ret =[]
     for record in raw_records:
         date = dateparser.parse(record['Data'])
         memo = record['Detalii tranzactie']
         line = StatementLine(date=date, memo=memo)
         if 'Credit' in  record:
             line.amount = D(record['Credit'].replace('.','').replace(',','.'))
             line.trntype = 'CREDIT'
         elif 'Debit' in record:
             line.amount = D(record['Debit'].replace('.','').replace(',','.'))
             line.trntype='DEBIT'
         else:
             raise ArgumentError
         if line.trntype=='CREDIT':
             r = re.compile('ordonator:?(?P<payee>.*)$', re.MULTILINE| re.IGNORECASE)
             m = r.search( memo)
             if m:
                 d = m.groupdict()
                 line.payee = d['payee']
                 line.trntype='XFER'
             #r = re.compile('din contul:?(?P<payee>.*)$', re.MULTILINE| re.IGNORECASE)
             #m = r.search( memo)
             #if m:
             #    d = m.groupdict()
             #    line.payee = d['payee']
             r = re.compile('referinta:?(?P<refnum>.*)$', re.MULTILINE| re.IGNORECASE)
             m = r.search( memo)
             if m:
                 d = m.groupdict()
                 line.refnum = line.check_no = d['refnum']
         line.id= generate_transaction_id(line)
         ret.append(line)
     return ret
Example #10
0
 def parse_record(self, mov):
     stat_line = StatementLine(None,
                               mov.data_contabile,
                               mov.descrizione_estesa,
                               Decimal(mov.accrediti) if mov.accrediti else
                               Decimal(mov.addebiti))
     stat_line.id = generate_transaction_id(stat_line)
     stat_line.date_user = mov.data_valuta
     stat_line.trntype = IntesaSanPaoloXlsxParser._get_transaction_type(mov)
     logging.debug(stat_line)
     return stat_line
    def parse_record(self, line):
        if len(line) < 5:
            return None
        elif len(line) < 12:
            # possibly meta information about the account
            if "BLZ" in line[0]:
                self.statement.bank_id = line[1]
            elif "Konto" in line[0]:
                self.statement.account_id = line[1]

            return None

        if line[9] == "Anfangssaldo":
            self.statement.start_date = self.parse_datetime(line[0])
            self.statement.start_balance = self.parse_float(line[11])
            return None
        elif line[9] == "Endsaldo":
            self.statement.end_date = self.parse_datetime(line[0])
            self.statement.end_balance = self.parse_float(line[11])
            return None
        elif line[0] == "Buchungstag":
            # it's the table header
            return None

        sl = StatementLine()
        sl.date = self.parse_datetime(line[0])
        sl.date_avail = self.parse_datetime(line[1])

        # Note: amount has no sign. We need to guess it later...
        sl.amount = self.parse_float(line[11])

        info = self.parse_transaction_info(line)
        sl.amount *= info["sign"]
        sl.trntype = info["ttype"]

        if "iban" in info:
            # additional bank information if present
            sl.bank_account_to = BankAccount(**self.parse_iban(info["iban"]))

        if line[10] != self.statement.currency:
            # different currency is used
            sl.currency = line[10]

        # remove additional spaces in the payee
        sl.payee = re.sub(" +", " ", line[3].replace("\n", " ").strip())[:32]

        # remove additional spaces in the memo
        sl.memo = re.sub(" +", " ", info["memo"].strip())

        # we need to generate an ID because nothing is given
        sl.id = generate_stable_transaction_id(sl)
        return sl
Example #12
0
    def parse_record(self, line):
        """
        Parse given transaction line and return StatementLine object

        """
        sl = StatementLine()
        sl.amount = Decimal(line['Betrag'].replace(',', '.'))
        # TODO trntype could be improved using 'Buchungstext'
        if sl.amount.is_signed():
            sl.trntype = 'DEBIT'
        else:
            sl.trntype = 'CREDIT'
        # .date: It is debatable whether to use 'Buchungstag' or 'Valutadatum'
        sl.date = self.parse_datetime(line['Valutadatum'])
        # .date_user is not contained in the original CSV

        # .payee becomes OFX.NAME which becomes "Description" in gnuCash
        # .memo  becomes OFX.MEMO which becomes "Notes"       in gnuCash
        # When .payee is empty, GnuCash imports .memo to "Description" and
        # keeps "Notes" empty
        #
        # OFX's <NAME> and <PAYEE> are distinct fields. But ofxstatement's
        # .payee is translated to OFX's <NAME>
        #
        # According to the OFX spec (version 2.1.1):
        #     <NAME>      Name of payee or description of transaction, A-32
        #                 Note: Provide NAME or PAYEE, not both
        #     <MEMO>      Extra information (not in <NAME>)
        #
        # I prefer to have a description in .payee because that's what it ends
        # up being in gnuCash.

        recipient = _truncate_str(line['Beguenstigter/Zahlungspflichtiger'])
        if not recipient:
            recipient = 'UNBEKANNT'
        sl.payee = "{}; {}".format(
            _truncate_str(line['Verwendungszweck']),
            recipient,
        )
        sl.memo = "{}; IBAN: {}; BIC: {}".format(
            line['Buchungstext'].strip(),
            line['Kontonummer/IBAN'].strip(),
            line['BIC (SWIFT-Code)'].strip(),
        )

        m = sha256()
        m.update(str(sl.date).encode('utf-8'))
        m.update(sl.payee.encode('utf-8'))
        m.update(sl.memo.encode('utf-8'))
        m.update(str(sl.amount).encode('utf-8'))

        # Shorten the hash to the first 16 digits just to make it more
        # manageable. It should still be enough.
        sl.id = str(abs(int(m.hexdigest(), 16)))[:16]

        return sl
Example #13
0
    def parse_record(self, row):
        row = take(5, row)

        stmt_line = StatementLine()
        stmt_line.date = self.parse_datetime(row[0])
        _ = self.parse_datetime(row[1])  # TODO: ???
        stmt_line.refnum = row[2]
        stmt_line.memo = row[3]
        stmt_line.amount = row[4]

        #
        # Looks like SEB formats description for card transactions so it includes the actual purchase date
        # within e.g. 'WIRSTRÖMS PU/14-12-31' and it means that description is 'WIRSTRÖMS PU' while the actual
        # card operation is 2014-12-31.
        #
        # P.S. Wirströms Irish Pub is our favorite pub in Stockholm: http://www.wirstromspub.se
        #
        m = re.match('(.*)/([0-9]{2}-[0-9]{2}-[0-9]{2})$', stmt_line.memo)
        if m:
            card_memo, card_date = m.groups()
            if self.brief:
                stmt_line.memo = card_memo
            stmt_line.date_user = datetime.strptime(card_date, '%y-%m-%d')

        stmt_line.id = generate_transaction_id(stmt_line)
        return stmt_line
Example #14
0
    def parse_record(self, row):

        id_idx = self.valid_header.index("Transaction ID")
        date_idx = self.valid_header.index("Date")
        memo_idx = self.valid_header.index("Name")
        refnum_idx = self.valid_header.index("Reference Txn ID")
        amount_idx = self.valid_header.index("Gross")
        payee_idx = self.valid_header.index("To Email Address")
        title_idx = self.valid_header.index("Item Title")

        stmt_line = StatementLine()
        stmt_line.id = row[id_idx]
        stmt_line.date = datetime.strptime(row[date_idx], self.date_format)
        stmt_line.memo = row[memo_idx]

        if self.analyze:
            memo_parts = [row[memo_idx]]

            payee = row[payee_idx]
            if payee and (payee.lower() == '*****@*****.**'):
                memo_parts.append(row[title_idx])

            stmt_line.memo = ' / '.join(filter(bool, memo_parts))

        stmt_line.refnum = row[refnum_idx]
        stmt_line.amount = atof(row[amount_idx].replace(" ", ""), self.locale)

        return stmt_line
    def _parse_line(self, ntry):
        sline = StatementLine()

        crdeb = _find(ntry, 'CdtDbtInd').text

        amtnode = _find(ntry, 'Amt')
        amt = self._parse_amount(amtnode, self.statement.currency)
        if crdeb == CD_DEBIT:
            amt = -amt
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm')
        else:
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm')

        sline.payee = payee.text
        sline.amount = amt

        dt = _find(ntry, 'ValDt')
        sline.date = self._parse_date(dt)

        bookdt = _find(ntry, 'BookgDt')
        sline.date_user = self._parse_date(bookdt)

        svcref = _find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef')
        sline.refnum = svcref.text

        rmtinf = _find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd')
        sline.memo = rmtinf.text

        return sline
    def parse_record(self, line):
        """Parse given transaction line and return StatementLine object
        """
        #print(line)
        sl = StatementLine()
        sl = super(PostFinanceBankParser, self).parse_record(line)

        if not line[3]:
            sl.amount = self.parse_float(line[2])
            sl.trntype = 'CREDIT'
        else:
            sl.amount = self.parse_float("-" + line[3])
            sl.trntype = 'DEBIT'

        sl.payee = sl.memo

        if payeesplit:  #TODO: enable via settings
            found = False
            for regex in payeefinder:
                m = regex.match(sl.payee)
                if m:
                    found = True
                    sl.payee = m.group(1)

                #if not found:
                #   print ("Found No PAYEE!" + sl.memo)

        return sl
 def parse_record(self, row):
     self.row_num += 1
     line = StatementLine()
     line.date = self.parse_datetime(row[0].value)
     line.date_user = self.parse_datetime(row[1].value)
     line.refnum = str(self.row_num)
     line.memo = row[2].value
     line.amount = row[3].value
     line.trntype = self.get_type(line)
     line.id = generate_transaction_id(line)
     return line
    def parse_record(self, line):
        # Free Headerline
        if self.cur_record <= 1:
            return None

        stmt_line = StatementLine()
        stmt_line.date = self.parse_datetime(line[0].strip())

        # Amount
        paid_out = -self.parse_amount(line[2])
        paid_in = self.parse_amount(line[3])
        stmt_line.amount = paid_out or paid_in

        reference = line[1].strip()
        trntype = False
        for prefix, transaction_type in TRANSACTION_TYPES.items():
            if reference.startswith(prefix):
                trntype = transaction_type
                break

        if not trntype:
            trntype = 'POS'  # Default: Debit card payment

        # It's ... pretty ugly, but I see no other way to do this than parse
        # the reference string because that's all the data we have.
        stmt_line.trntype = trntype
        if trntype == 'POS':
            stmt_line.payee, stmt_line.memo = self.parse_payee_memo(reference)
        elif reference.startswith('Cash at '):
            stmt_line.payee, stmt_line.memo = self.parse_payee_memo(
                reference[8:])
        elif reference.startswith('To ') or reference.startswith('From '):
            stmt_line.payee = self.parse_value(
                reference[reference.find(' '):], 'payee'
            )
        else:
            stmt_line.memo = self.parse_value(reference, 'memo')

        # Notes (from Apr-2018)
        if len(line) > 8 and line[8].strip():
            if not stmt_line.memo:
                stmt_line.memo = u''
            elif len(stmt_line.memo.strip()) > 0:
                stmt_line.memo += u' '
            stmt_line.memo += u'({})'.format(line[8].strip())

        return stmt_line
Example #19
0
    def _parse_line(self, ntry: ET.Element) -> Optional[StatementLine]:
        sline = StatementLine()

        crdeb = self._findstrict(ntry, "CdtDbtInd").text

        amtnode = self._findstrict(ntry, "Amt")
        amt_ccy = amtnode.get("Ccy")

        if amt_ccy != self.statement.currency:
            # We can't include amounts with incompatible currencies into the
            # statement.
            return None

        amt = self._parse_amount(amtnode)
        if crdeb == CD_DEBIT:
            amt = -amt
            payee = self._find(ntry, "NtryDtls/TxDtls/RltdPties/Cdtr/Nm")
        else:
            payee = self._find(ntry, "NtryDtls/TxDtls/RltdPties/Dbtr/Nm")

        sline.payee = payee.text if payee is not None else None
        sline.amount = amt

        dt = self._find(ntry, "ValDt")
        sline.date = self._parse_date(dt)

        bookdt = self._find(ntry, "BookgDt")
        sline.date_user = self._parse_date(bookdt)

        svcref = self._find(ntry, "NtryDtls/TxDtls/Refs/AcctSvcrRef")
        if svcref is None:
            svcref = self._find(ntry, "AcctSvcrRef")
        if svcref is not None:
            sline.refnum = svcref.text

        # Try to find memo from different possible locations
        refinf = self._find(ntry, "NtryDtls/TxDtls/RmtInf/Strd/CdtrRefInf/Ref")
        rmtinf = self._find(ntry, "NtryDtls/TxDtls/RmtInf/Ustrd")
        addinf = self._find(ntry, "AddtlNtryInf")
        if refinf is not None:
            sline.memo = refinf.text
        elif rmtinf is not None:
            sline.memo = rmtinf.text
        elif addinf is not None:
            sline.memo = addinf.text

        return sline
    def parse_record(self, line):
        # Namespace stuff
        namespaces = {'ns': line.tag[1:].partition("}")[0]}

        # Get all fields
        type_code = line.find('ns:TypeCode', namespaces=namespaces).text
        date = line.find('ns:BookDate', namespaces=namespaces).text
        c_or_d = line.find('ns:CorD', namespaces=namespaces).text
        amount = line.find('ns:AccAmt', namespaces=namespaces).text
        id = line.find('ns:BankRef', namespaces=namespaces).text
        note = line.find('ns:PmtInfo', namespaces=namespaces).text

        # Payee name
        payee_name = None
        payee = line.find('ns:CPartySet', namespaces=namespaces)
        if payee:
            payee_account = payee.find('ns:AccHolder', namespaces=namespaces)
            if payee_account:
                payee_name = payee_account.find('ns:Name', namespaces=namespaces).text

        # Create statement line
        stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount))
        stmt_line.payee = payee_name

        # Credit & Debit stuff
        stmt_line.trntype = "DEP"
        if c_or_d == 'D':
            stmt_line.amount = -stmt_line.amount
            stmt_line.trntype = "DEBIT"

        # Various types
        if type_code == 'CHOU':
            stmt_line.trntype = "ATM"
        elif type_code == 'MEMD':
            stmt_line.trntype = "SRVCHG"
        elif type_code == 'OUTP':
            stmt_line.trntype = "PAYMENT"
        elif type_code == 'INP':
            stmt_line.trntype = "XFER"

#        # Check if paid by card
#         m = CARD_PURCHASE_RE.match(stmt_line.memo)
#         if m:
#             # this is an electronic purchase. extract some useful
#             # information from memo field
#             date = m.group(1).split('/')
#             date = '%s-%s-%s' % (date[2], date[1], date[0])
#             stmt_line.date_user = self.parse_datetime(date)

        print(stmt_line, stmt_line.trntype)
        return stmt_line
    def parse(self):
        statement = Statement(bank_id='321081669', currency='USD')

        with open(self.filename) as f:
            for row in csv.DictReader(f):
                line = StatementLine(id=row['Transaction Number'],
                                     date=self.parse_datetime(row['Date']),
                                     memo=row['Statement Description'],
                                     amount=Decimal(row['Debit']
                                                    or row['Credit']))

                line.payee = row['Description']
                line.check_no = row['Check Number']
                line.trntype = self.guess_type(line.payee, line.amount)
                statement.lines.append(line)

        return statement
Example #22
0
    def parse_record(self, line):
        """Extracts the transaction data from the given line parsed from the JSON file.

        :param line: A transaction line from the parsed JSON file.
        :return: A new StatementLine filled by the given line's data.
        """
        amount = to_float(line['amounts']['amount'])
        if self.statement.filter_zeros and is_zero(amount):
            return None

        memo = line['description']
        datetime = ts_to_datetime(line['times']['when_recorded'])
        id = line['uuid']
        ledger_amount = convert_debit(amount, line['bookkeeping_type'])
        stmt_line = StatementLine(id=id, date=datetime, memo=memo, amount=ledger_amount)
        stmt_line.end_balance = to_float(line['running_balance'])
        return stmt_line
Example #23
0
    def parse_record(self, line):
        # Namespace stuff
        namespaces = {'ns': line.tag[1:].partition("}")[0]}

        # Get all fields
        type_code = line.find('ns:TypeCode', namespaces=namespaces).text
        date = line.find('ns:BookDate', namespaces=namespaces).text
        c_or_d = line.find('ns:CorD', namespaces=namespaces).text
        amount = line.find('ns:AccAmt', namespaces=namespaces).text
        id = line.find('ns:BankRef', namespaces=namespaces).text
        note = line.find('ns:PmtInfo', namespaces=namespaces).text

        # Payee name
        payee_name = None
        payee = line.find('ns:CPartySet', namespaces=namespaces)
        if payee:
            payee_account = payee.find('ns:AccHolder', namespaces=namespaces)
            if payee_account:
                payee_name = payee_account.find('ns:Name', namespaces=namespaces).text

        # Create statement line
        stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount))
        stmt_line.payee = payee_name

        # Credit & Debit stuff
        stmt_line.trntype = "DEP"
        if c_or_d == 'D':
            stmt_line.amount = -stmt_line.amount
            stmt_line.trntype = "DEBIT"

        # Various types
        if type_code == 'MEMD':
            stmt_line.trntype = "SRVCHG"
        elif type_code == 'OUTP':
            stmt_line.trntype = "PAYMENT"

        # Check if paid by card
        m = CARD_PURCHASE_RE.match(stmt_line.memo)
        if m:
            # this is an electronic purchase. extract some useful
            # information from memo field
            date = m.group(1).split('/')
            date = '%s-%s-%s' % (date[2], date[1], date[0])
            stmt_line.date_user = self.parse_datetime(date)

        # DEBUG
        if self.debug:
            print(stmt_line, stmt_line.trntype)

        return stmt_line
    def parse_record(self, line):
        # Namespace stuff
        namespaces = {'ns': line.tag[1:].partition("}")[0]}

        # Get all fields
        type_code = line.find('ns:TypeCode', namespaces=namespaces).text
        date = line.find('ns:BookDate', namespaces=namespaces).text
        c_or_d = line.find('ns:CorD', namespaces=namespaces).text
        amount = line.find('ns:AccAmt', namespaces=namespaces).text
        id = line.find('ns:BankRef', namespaces=namespaces).text
        note = line.find('ns:PmtInfo', namespaces=namespaces).text

        # Payee name
        payee_name = None
        payee = line.find('ns:CPartySet', namespaces=namespaces)
        if payee:
            payee_account = payee.find('ns:AccHolder', namespaces=namespaces)
            if payee_account:
                payee_name = payee_account.find('ns:Name', namespaces=namespaces).text

        # Create statement line
        stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount))
        stmt_line.payee = payee_name

        # Credit & Debit stuff
        stmt_line.trntype = "DEP"
        if c_or_d == 'D':
            stmt_line.amount = -stmt_line.amount
            stmt_line.trntype = "DEBIT"

        # Various types
        if type_code == 'CHOU':
            stmt_line.trntype = "ATM"
        elif type_code == 'MEMD':
            stmt_line.trntype = "SRVCHG"
        elif type_code == 'OUTP':
            stmt_line.trntype = "PAYMENT"
        elif type_code == 'INP':
            stmt_line.trntype = "XFER"

        # DEBUG
        if self.debug:
            print(stmt_line, stmt_line.trntype)

        return stmt_line
Example #25
0
    def _parse_line(self, ntry):
        sline = StatementLine()

        crdeb = _find(ntry, 'CdtDbtInd').text

        amtnode = _find(ntry, 'Amt')
        amt = self._parse_amount(amtnode, self.statement.currency)
        if crdeb == CD_DEBIT:
            amt = -amt
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm')
        else:
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm')

        sline.payee = payee.text
        sline.amount = amt

        dt = _find(ntry, 'ValDt')
        sline.date = self._parse_date(dt)

        bookdt = _find(ntry, 'BookgDt')
        sline.date_user = self._parse_date(bookdt)

        svcref = _find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef')
        sline.refnum = svcref.text

        rmtinf = _find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd')
        sline.memo = rmtinf.text

        return sline
Example #26
0
    def parse_record(self, line):
        statement_line = StatementLine()
        for field, col_number in self.mappings.items():
            state = State(field, self.parse_value)

            raw_value = self.get_nth_value(line, col_number)
            value = raw_value.flatmap(state.execute)
            setattr(statement_line, field, value)
        return statement_line
Example #27
0
 def parse_record(self, line):
     stmt_line = StatementLine()
     for field, col in self.mappings.items():
         if col >= len(line):
             raise ValueError("Cannot find column %s in line of %s items " % (col, len(line)))
         rawvalue = line[col]
         value = self.parse_value(rawvalue, field)
         setattr(stmt_line, field, value)
     return stmt_line
Example #28
0
 def parse_record(self, row):
     stmt_line = StatementLine()
     stmt_line.date = self.parse_datetime(row[0])
     stmt_line.date_user = self.parse_datetime(row[1])
     stmt_line.memo = row[2]
     stmt_line.amount = self.parse_float(row[3])
     stmt_line.id = generate_transaction_id(stmt_line)
     return stmt_line
Example #29
0
    def test_ofxWriter(self):

        # Create sample statement:
        statement = Statement("BID", "ACCID", "LTL")
        statement.lines.append(StatementLine(
            "1", datetime(2012, 2, 12), "Sample 1", 15.4))
        line = StatementLine("2", datetime(2012, 2, 12), "Sample 2", 25.0)
        line.payee = ''
        line.bank_account_to = BankAccount("SNORAS", "LT1232")
        line.bank_account_to.branch_id = "VNO"
        statement.lines.append(line)

        # Create writer:
        writer = ofx.OfxWriter(statement)

        # Set the generation time so it is always predictable
        writer.genTime = datetime(2012, 3, 3, 0, 0, 0)

        assert prettyPrint(writer.toxml()) == SIMPLE_OFX
Example #30
0
    def parse_record(self, line):
        """Extracts the transaction data from the given line parsed from the JSON file.

        :param line: A transaction line from the parsed JSON file.
        :return: A new StatementLine filled by the given line's data.
        """
        amount = to_float(line['amounts']['amount'])
        if self.statement.filter_zeros and is_zero(amount):
            return None

        memo = line['description']
        datetime = ts_to_datetime(line['times']['when_recorded'])
        id = line['uuid']
        ledger_amount = convert_debit(amount, line['bookkeeping_type'])
        stmt_line = StatementLine(id=id,
                                  date=datetime,
                                  memo=memo,
                                  amount=ledger_amount)
        stmt_line.end_balance = to_float(line['running_balance'])
        return stmt_line
    def _parse_line(self, ntry):
        sline = StatementLine()

        crdeb = self._find(ntry, 'CdtDbtInd').text

        amtnode = self._find(ntry, 'Amt')
        amt_ccy = amtnode.get('Ccy')

        if amt_ccy != self.statement.currency:
            # We can't include amounts with incompatible currencies into the
            # statement.
            return None

        amt = self._parse_amount(amtnode)
        if crdeb == CD_DEBIT:
            amt = -amt
            payee = self._find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm')
        else:
            payee = self._find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm')

        sline.payee = payee.text if payee is not None else None
        sline.amount = amt

        dt = self._find(ntry, 'ValDt')
        sline.date = self._parse_date(dt)

        bookdt = self._find(ntry, 'BookgDt')
        sline.date_user = self._parse_date(bookdt)

        svcref = self._find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef')
        if svcref is None:
            svcref = self._find(ntry, 'AcctSvcrRef')
        if svcref is None:
            svcref = self._find(ntry, 'NtryDtls/TxDtls/Refs/MsgId')
        if svcref is not None:
            sline.refnum = svcref.text

        # Try to find memo from different possible locations
        rmtinf = self._find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd')
        addinf = self._find(ntry, 'AddtlNtryInf')
        if rmtinf is not None:
            sline.memo = rmtinf.text
        elif addinf is not None:
            sline.memo = addinf.text

        return sline
    def try_cache(self, stmt_line: StatementLine) -> None:
        account_id: str = str(self.statement.account_id)
        key: TransactionKey
        data: TransactionData

        # The PDF may differ from the OFX by a different date
        # or a switch from payee to memo or otherwise: forget them
        for dt in [
                stmt_line.accounting_date, stmt_line.operation_date,
                stmt_line.value_date
        ]:
            check_no: Optional[str]
            name: Optional[str]
            if stmt_line.payee == 'VIREMENT SEPA' and not (stmt_line.check_no):
                check_no = None
                name = stmt_line.memo
            else:
                check_no = stmt_line.check_no
                name = None

            key = Parser.transaction_key(account_id, check_no, dt,
                                         stmt_line.amount, name)

            if key in self.cache:
                data = self.cache[key]
                logger.debug('Found data %r for key %r', data, key)
                stmt_line.date = dt
                stmt_line.id = data.id
                stmt_line.payee = data.name
                stmt_line.memo = data.memo
                return
            else:
                logger.debug('Did not find value for key %r', key)

        if not self.cache_printed:
            self.print_cache('try_cache: no data found')
            self.cache_printed = True

        return
Example #33
0
    def parse_record(self, line):
        """Parse given transaction line and return StatementLine object
        """
        stmt_line = StatementLine()

        # date field
        stmt_line.date = self.xls_date(int(line[0]))

        # amount field
        if line[2]:
            income = line[2]
            outcome = 0
        elif line[3]:
            outcome = line[3]
            income = 0
        stmt_line.amount = income - outcome

        # transaction type field
        if (stmt_line.amount < 0):
            stmt_line.trntype = "DEBIT"
        else:
            stmt_line.trntype = "CREDIT"

        # name field
        # set <NAME> field with content of column 'Descrizione'
        # only if proper option is active
        if self.info2name:
            stmt_line.payee = line[4]

        # memo field
        stmt_line.memo = line[5]
        # concat "Descrizione" column at the end of <MEMO> field
        # if proper option is present
        if self.info2memo:
            if stmt_line.memo != '' and line[2] != '':
                stmt_line.memo += ' - '
            stmt_line.memo += line[2]

        # id field
        stmt_line.id = generate_transaction_id(stmt_line)

        #print(str(stmt_line))
        return stmt_line
Example #34
0
    def parse_record(self, line):
        # Namespace stuff
        namespaces = {"ns": line.tag[1:].partition("}")[0]}

        # Get all fields
        type_code = line.find("ns:TypeCode", namespaces=namespaces).text
        date = line.find("ns:BookDate", namespaces=namespaces).text
        c_or_d = line.find("ns:CorD", namespaces=namespaces).text
        amount = line.find("ns:AccAmt", namespaces=namespaces).text
        id = line.find("ns:BankRef", namespaces=namespaces).text
        note = line.find("ns:PmtInfo", namespaces=namespaces).text

        # Payee name
        payee_name = None
        payee = line.find("ns:CPartySet", namespaces=namespaces)
        if payee:
            payee_account = payee.find("ns:AccHolder", namespaces=namespaces)
            if payee_account:
                payee_name = payee_account.find("ns:Name", namespaces=namespaces).text

        # Create statement line
        stmt_line = StatementLine(id, self.parse_datetime(date), note, self.parse_float(amount))
        stmt_line.payee = payee_name

        # Credit & Debit stuff
        stmt_line.trntype = "DEP"
        if c_or_d == "D":
            stmt_line.amount = -stmt_line.amount
            stmt_line.trntype = "DEBIT"

        # Various types
        if type_code == "MEMD":
            stmt_line.trntype = "SRVCHG"
        elif type_code == "OUTP":
            stmt_line.trntype = "PAYMENT"

        #  Check if paid by card
        m = CARD_PURCHASE_RE.match(stmt_line.memo)
        if m:
            # this is an electronic purchase. extract some useful
            # information from memo field
            date = m.group(1).split("/")
            date = "%s-%s-%s" % (date[2], date[1], date[0])
            stmt_line.date_user = self.parse_datetime(date)

        # print(stmt_line)
        return stmt_line
 def parse_record(self, row):
     self.row_num += 1
     line = StatementLine()
     line.date = self.parse_datetime(row[0].value)
     line.date_user = self.parse_datetime(row[1].value)
     line.refnum = str(self.row_num)
     line.memo = row[2].value
     line.amount = row[3].value
     line.trntype = self.get_type(line)
     if self.statement.start_balance is None and self.row_num == 1:
         self.statement.start_balance = row[4].value - line.amount
         self.statement.start_date = line.date_user
     self.statement.end_balance = row[4].value
     line.id = self.generate_transaction_id(line)
     if line.id in self.seen:
         log.warn(
             "Transaction with duplicate FITID generated:\n%s\n%s\n\n" %
             (line, self.seen[line.id]))
     else:
         self.seen[line.id] = line
     return line
    def parse_record(self, line):
        """Parse given transaction line and return StatementLine object
        """
        #print(line)
        sl = StatementLine()
        sl = super(PostFinanceCreditParser, self).parse_record(line)

        #print(line)
        if not line[3]:
            sl.amount = self.parse_float(line[4])
            sl.trntype = 'CREDIT'
        else:
            sl.amount = self.parse_float("-" + line[3])
            sl.trntype = 'DEBIT'

        sl.payee = sl.memo

        return sl
    def parse_record(self, stmt_line: StatementLine) -> StatementLine:
        """Parse given transaction line and return StatementLine object
        """
        def add_years(d: date, years: int) -> date:
            """Return a date that's `years` years after the date
            object `d`. Return the same calendar date (month and day) in the
            destination year, if it exists, otherwise use the following day
            (thus changing February 29 to March 1).

            """
            return d.replace(year=d.year + years, month=3, day=1) \
                if d.month == 2 and d.day == 29 \
                else d.replace(year=d.year + years)

        def get_date(d_m: str) -> date:
            assert self.statement.end_date
            # Without a year it will be 1900 so add the year
            d_m_y: str = "{}/{}".format(d_m, self.statement.end_date.year)
            d: date = datetime.strptime(d_m_y, '%d/%m/%Y').date()
            if d > self.statement.end_date:
                d = add_years(d, -1)
            assert d <= self.statement.end_date
            return d

        logger.debug('Statement line: %r', stmt_line)

        # Remove zero-value notifications
        if stmt_line.amount == 0:  # pragma: no cover
            return None

        stmt_line.accounting_date = get_date(stmt_line.accounting_date)
        stmt_line.operation_date = get_date(stmt_line.operation_date)
        stmt_line.value_date = get_date(stmt_line.value_date)
        self.try_cache(stmt_line)
        if not stmt_line.id:
            stmt_line.date = stmt_line.accounting_date
            account_id = self.statement.account_id
            stmt_line.id = \
                generate_unique_transaction_id(stmt_line,
                                               self.unique_id_sets[account_id])
            m = re.match(r'([0-9a-f]+)(-\d+)?$', stmt_line.id)
            assert m, "Id should match hexadecimal digits, \
optionally followed by a minus and a counter: '{}'".format(stmt_line.id)
            if m.group(2):
                counter = int(m.group(2)[1:])
                # include counter so the memo gets unique
                stmt_line.memo = stmt_line.memo + ' #' + str(counter + 1)

        return stmt_line
Example #38
0
    def _parse_line(self, ntry):
        sline = StatementLine()

        crdeb = _find(ntry, 'CdtDbtInd').text

        amtnode = _find(ntry, 'Amt')
        amt = self._parse_amount(amtnode)
        if crdeb == CD_DEBIT:
            amt = -amt
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Cdtr/Nm')
        else:
            payee = _find(ntry, 'NtryDtls/TxDtls/RltdPties/Dbtr/Nm')
        if payee is not None:
            payee = payee.text

        sline.payee = payee
        sline.amount = amt

        dt = _find(ntry, 'ValDt')
        sline.date = self._parse_date(dt)

        bookdt = _find(ntry, 'BookgDt')
        sline.date_user = self._parse_date(bookdt)

        svcref = _find(ntry, 'NtryDtls/TxDtls/Refs/AcctSvcrRef')
        sline.refnum = getattr(svcref, 'text', None)

        rmtinf = _find(ntry, 'NtryDtls/TxDtls/RmtInf/Ustrd')
        sline.memo = rmtinf.text if rmtinf.text else ''

        addtlinf_node = _find(ntry, 'NtryDtls/TxDtls/AddtlTxInf')
        addtlinf = self._parse_addtlinf(addtlinf_node)

        if 'VÁSÁRLÁS KÁRTYÁVAL' == addtlinf and not sline.payee:
            sline.payee = _trim_payee(sline.memo)

        sline.memo += ' ' + addtlinf

        return sline
Example #39
0
    def test_ofxWriter(self) -> None:

        # Create sample statement:
        statement = Statement("BID", "ACCID", "LTL")
        statement.lines.append(
            StatementLine("1", datetime(2012, 2, 12), "Sample 1",
                          Decimal("15.4")))
        line = StatementLine("2", datetime(2012, 2, 12), "Sample 2",
                             Decimal("25.0"))
        line.payee = ""
        line.bank_account_to = BankAccount("SNORAS", "LT1232")
        line.bank_account_to.branch_id = "VNO"
        line.currency = Currency("USD")
        line.orig_currency = Currency("EUR", Decimal("3.4543"))
        statement.lines.append(line)

        # Create writer:
        writer = ofx.OfxWriter(statement)

        # Set the generation time so it is always predictable
        writer.genTime = datetime(2012, 3, 3, 0, 0, 0)

        assert prettyPrint(writer.toxml()) == SIMPLE_OFX
Example #40
0
    def parse_record(self, transaction: Transaction) -> StatementLine:
        """Parse given transaction line and return StatementLine object
        """
        logger.debug('transaction:\n' + pformat(transaction, indent=4))
        stmt_line = None

        # Use str() to prevent rounding errors
        bank_account_to = transaction.data.get('customer_reference')
        amount = Decimal(str(transaction.data['amount'].amount))
        memo = transaction.data['transaction_details']
        memo = memo.replace("\n", '')
        memo = memo.replace(transaction.data['customer_reference'], '', 1)
        memo = memo.replace(transaction.data['extra_details'], '', 1).strip()
        memo = memo if memo != '' else 'UNKNOWN'
        payee = None
        if transaction.data['customer_reference'] != ''\
           and transaction.data['extra_details'] != '':
            payee = "{1} ({0})".format(transaction.data['customer_reference'],
                                       transaction.data['extra_details'])

        date = transaction.data['date']

        # Remove zero-value notifications
        if amount != 0:
            stmt_line = StatementLine(date=date, memo=memo, amount=amount)
            stmt_line.id = \
                generate_unique_transaction_id(stmt_line, self.unique_id_set)
            m = re.match(r'([0-9a-f]+)(-\d+)?$', stmt_line.id)
            assert m, "Id should match hexadecimal digits, \
optionally followed by a minus and a counter: '{}'".format(stmt_line.id)
            if m.group(2):
                counter = int(m.group(2)[1:])
                # include counter so the memo gets unique
                stmt_line.memo = stmt_line.memo + ' #' + str(counter + 1)

            stmt_line.payee = payee
            if bank_account_to:
                stmt_line.bank_account_to = \
                    BankAccount(bank_id=None,
                                acct_id=bank_account_to)

        return stmt_line
Example #41
0
    def parse(self):
        DATA_DATE = 'data_date'
        DATA_AMT = 'data_amt'
        DATA_DSC = 'data_dsc'
        DATA_MEMO = 'data_mem'
        DATA_BAL = 'data_bal'

        def parser_old(filename):
            def parse_tr(tr):
                def get_header(tr, n):
                    return str(
                        tr.find_all(headers='header' + str(n))[0].string)

                hdrs = tr.find_all(headers=True)
                assert len(hdrs) in [0, 7]
                if len(hdrs) == 0:
                    cont = True
                    date_or_comm = str(tr.find_all(colspan='5')[0].string)
                    desc = None
                    paym = None
                    depo = None
                    bal = None
                else:
                    cont = False
                    date_or_comm = dateutil.parser.parse(get_header(tr, 1),
                                                         dayfirst=True)
                    desc = get_header(tr, 2)

                    paym = self.get_float(get_header(tr, 5))
                    depo = self.get_float(get_header(tr, 6))

                    assert (depo is None) != (
                        paym is None
                    ), 'Either depo or paym need to exist, but not both: ' + str(
                        tr)

                    bal = self.get_float(get_header(tr, 7))

                return cont, date_or_comm, desc, paym, depo, bal

            with open(filename, 'r', encoding='iso-8859-8') as f:
                soup = BeautifulSoup(f, 'lxml')
                statement = Statement(currency='ILS')
                data = []
                for tr in soup.find_all(
                        'table',
                        id='mytable_body')[0].find_all(id='TR_ROW_BANKTABLE'):
                    cont, date_or_comm, desc, paym, depo, bal = parse_tr(tr)
                    if cont:
                        data[-1][DATA_MEMO] = date_or_comm
                    else:
                        new_line = {}

                        new_line[DATA_DATE] = date_or_comm
                        new_line[DATA_DSC] = desc
                        new_line[
                            DATA_AMT] = -paym if paym is not None else depo
                        new_line[DATA_BAL] = bal
                        new_line[DATA_MEMO] = None

                        data.append(new_line)
                return data

        def parser_new(filename):
            with open(filename, 'r', encoding='iso-8859-8') as f:
                bs = BeautifulSoup(f, 'lxml')
            trs = bs.find_all('table', i__d='trBlueOnWhite12')[0].find_all(
                'tr', recursive=False)

            data = []
            for tr in trs[1:]:
                new_line = {}
                tds = tr.find_all('td')
                if len(tds) != 7:
                    continue
                new_line[DATA_DATE] = dateutil.parser.parse(str(tds[0].string),
                                                            dayfirst=True)
                new_line[DATA_DSC] = str(tds[1].string)

                paym = self.get_float(str(tds[4].string))
                depo = self.get_float(str(tds[5].string))
                assert (depo is None) != (
                    paym is None
                ), 'Either depo or paym need to exist, but not both: ' + str(
                    tds)
                new_line[DATA_AMT] = -paym if paym is not None else depo

                new_line[DATA_BAL] = self.get_float(str(tds[6].string))

                new_line[DATA_MEMO] = None

                data.append(new_line)

            return data

        def parser_xslx(filename):
            with open(filename, 'rb') as f:
                ws = openpyxl.load_workbook(f).worksheets[0]

            iterrows = iter(ws.rows)
            for _ in range(6):
                next(iterrows)

            data = []
            for row in iterrows:
                new_line = {}
                new_line[DATA_DATE] = dateutil.parser.parse(str(row[0].value),
                                                            dayfirst=False)
                new_line[DATA_DSC] = str(row[1].value)

                paym = self.get_float(str(row[3].value))
                depo = self.get_float(str(row[4].value))
                assert (depo is None) != (
                    paym is
                    None), 'Either depo or paym need to exist, but not both: '
                new_line[DATA_AMT] = -paym if paym is not None else depo

                new_line[DATA_BAL] = self.get_float(str(row[5].value))

                new_line[DATA_MEMO] = " ".join(
                    [str(c.value) for c in row[7:] if c.value is not None])

                data.append(new_line)

            return data

        PARSERS = [parser_old, parser_new, parser_xslx]

        v = self.detect_version()
        assert v is not None, "Unsupported file %s" % self.filename
        self.log("Detected file of version %d" % v)

        data = PARSERS[v](self.filename)
        self.log('Found %d transactions' % len(data))

        stmnt = Statement(currency='ILS')
        for d in data:
            stmt_line = StatementLine(date=d[DATA_DATE], amount=d[DATA_AMT])
            #stmt_line.end_balance = d[DATA_BAL] #TODO: conf
            stmt_line.payee = d[DATA_DSC]
            if d[DATA_MEMO] is not None:
                stmt_line.memo = d[DATA_MEMO]

            if stmt_line.payee.startswith('משיכה'):
                stmt_line.trntype = 'ATM'
            elif stmt_line.payee.startswith('שיק'):
                stmt_line.trntype = 'CHECK'
            else:
                stmt_line.trntype = "CASH" if d[DATA_AMT] < 0 else "DEP"

            stmt_line.assert_valid()
            stmnt.lines.append(stmt_line)
        return stmnt
    def parse_record(self, line):
        if self.cur_record < 2:
            return None

        m = None
        parse_info = None

        if len(line) == 11:
            m = self.mt940_mappings
            parse_info = self.parse_transaction_info_mt940
        elif len(line) == 17:
            m = self.camt_mappings
            parse_info = self.parse_transaction_info_camt
        else:
            raise ValueError("invalid input line: '%s'" % line)

        if self.statement.account_id is None:
            self.statement.account_id = line[m["accid"]]

        sl = StatementLine()
        sl.date_avail = self.parse_datetime(line[m["valdate"]])
        if len(line[m["date"]]) > 0:
            sl.date = self.parse_datetime(line[m["date"]])
        else
            sl.date = sl.date_avail
        sl.amount = self.parse_float(line[m["amount"]])
        sl.trntype = self.parse_transaction_type(sl.amount,
                                                 line[m["btext"]])

        # remove leading or all) zeros
        line[m["toaccid"]] = line[m["toaccid"]].lstrip('0')

        if len(line[m["toaccid"]]) > 0 and len(line[m["tobankid"]]) > 0:
            # additional bank information is present
            splitted = self.parse_iban(line[m["toaccid"]])
            if splitted:
                sl.bank_account_to = BankAccount(**splitted)
            else:
                sl.bank_account_to = BankAccount(line[m["tobankid"]],
                                                 line[m["toaccid"]])

        if line[m["currency"]] != self.statement.currency:
            # different currency is used
            sl.currency = line[m["currency"]]

        # remove additional spaces in the payee
        sl.payee = re.sub(' +', ' ', line[m["payee"]])[:32]

        info = parse_info(line)
        # remove additional spaces in the memo
        sl.memo = "%s: %s" % (line[m["btext"]],
                              re.sub(' +', ' ', info["memo"].strip()))

        # we need to generate an ID because nothing is given
        sl.id = generate_stable_transaction_id(sl)
        return sl
 def testAmount(self):
     """Only amount is set"""
     sline = StatementLine()
     sline.amount = 123.45
     self.assertEqual(self.amt_hash, generate_stable_transaction_id(sline))
Example #44
0
    def parse(self):
        DATA_DATE = 'data_date'
        DATA_AMT = 'data_amt'
        DATA_DSC = 'data_dsc'
        DATA_MEMO = 'data_mem'
        DATA_BAL = 'data_bal'

        def parser_old(filename):

            def parse_tr(tr):
                def get_header(tr, n):
                    return str(tr.find_all(headers='header'+str(n))[0].string)

                hdrs = tr.find_all(headers=True)
                assert len(hdrs) in [0, 7]
                if len(hdrs) == 0:
                    cont = True
                    date_or_comm = str(tr.find_all(colspan='5')[0].string)
                    desc = None
                    paym = None
                    depo = None
                    bal = None
                else:
                    cont = False
                    date_or_comm = dateutil.parser.parse(get_header(tr, 1), dayfirst=True)
                    desc = get_header(tr, 2)

                    paym = self.get_float(get_header(tr, 5))
                    depo = self.get_float(get_header(tr, 6))

                    assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: ' + str(tr)

                    bal = self.get_float(get_header(tr, 7))

                return cont, date_or_comm, desc, paym, depo, bal

            with open(filename, 'r', encoding='iso-8859-8') as f:
                soup = BeautifulSoup(f, 'lxml')
                statement = Statement(currency='ILS')
                data = []
                for tr in soup.find_all('table', id='mytable_body')[0].find_all(id='TR_ROW_BANKTABLE'):
                    cont, date_or_comm, desc, paym, depo, bal = parse_tr(tr)
                    if cont:
                        data[-1][DATA_MEMO] = date_or_comm
                    else:
                        new_line = {}

                        new_line[DATA_DATE] = date_or_comm
                        new_line[DATA_DSC] = desc
                        new_line[DATA_AMT] = -paym if paym is not None else depo
                        new_line[DATA_BAL] = bal
                        new_line[DATA_MEMO] = None

                        data.append(new_line)
                return data

        def parser_new(filename):
            with open(filename, 'r', encoding='iso-8859-8') as f:
                bs = BeautifulSoup(f, 'lxml')
            trs = bs.find_all('table', i__d='trBlueOnWhite12')[0].find_all('tr', recursive=False)

            data = []
            for tr in trs[1:]:
                new_line = {}
                tds = tr.find_all('td')
                if len(tds) != 7:
                    continue
                new_line[DATA_DATE] = dateutil.parser.parse(str(tds[0].string), dayfirst=True)
                new_line[DATA_DSC] = str(tds[1].string)

                paym = self.get_float(str(tds[4].string))
                depo = self.get_float(str(tds[5].string))
                assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: ' + str(tds)
                new_line[DATA_AMT] = -paym if paym is not None else depo

                new_line[DATA_BAL] = self.get_float(str(tds[6].string))

                new_line[DATA_MEMO] = None

                data.append(new_line)

            return data

        def parser_xslx(filename):
            with open(filename, 'rb') as f:
                ws = openpyxl.load_workbook(f).worksheets[0]

            iterrows = iter(ws.rows)
            for _ in range(6):
                next(iterrows)

            data = []
            for row in iterrows:
                new_line = {}
                new_line[DATA_DATE] = dateutil.parser.parse(str(row[0].value), dayfirst=False)
                new_line[DATA_DSC] = str(row[1].value)

                paym = self.get_float(str(row[3].value))
                depo = self.get_float(str(row[4].value))
                assert (depo is None) != (paym is None), 'Either depo or paym need to exist, but not both: '
                new_line[DATA_AMT] = -paym if paym is not None else depo

                new_line[DATA_BAL] = self.get_float(str(row[5].value))

                new_line[DATA_MEMO] = " ".join([str(c.value) for c in row[7:] if c.value is not None])

                data.append(new_line)

            return data
        PARSERS = [parser_old, parser_new, parser_xslx]

        v = self.detect_version()
        assert v is not None, "Unsupported file %s" % self.filename
        self.log("Detected file of version %d" % v)

        data = PARSERS[v](self.filename)
        self.log('Found %d transactions' % len(data))

        stmnt = Statement(currency='ILS')
        for d in data:
            stmt_line = StatementLine(date=d[DATA_DATE], amount=d[DATA_AMT])
            #stmt_line.end_balance = d[DATA_BAL] #TODO: conf
            stmt_line.payee = d[DATA_DSC]
            if d[DATA_MEMO] is not None:
                stmt_line.memo = d[DATA_MEMO]

            if stmt_line.payee.startswith('משיכה'):
                stmt_line.trntype = 'ATM'
            elif stmt_line.payee.startswith('שיק'):
                stmt_line.trntype = 'CHECK'
            else:
                stmt_line.trntype = "CASH" if d[DATA_AMT] < 0 else "DEP"

            stmt_line.assert_valid()
            stmnt.lines.append(stmt_line)
        return stmnt