# -*- coding: utf-8 -*- # This file is part of the cashbook-module from m-ds for Tryton. # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. from trytond.pool import Pool from trytond.model import Model from trytond.i18n import gettext from decimal import Decimal from datetime import datetime class QifTool(Model): 'QIF Tool' __name__ = 'cashbook_dataexchange.qiftool' @classmethod def split_by_type(cls, qifdata): """ split file-content by type """ lines = qifdata.split('\n') blocks = {} current_type = None for line in lines: if line.startswith('!Type:'): current_type = line[len('!Type:'):].strip() else : if current_type is None: continue if not current_type in blocks.keys(): blocks[current_type] = [] blocks[current_type].append(line.strip()) for block in blocks.keys(): blocks[block] = '\n'.join(blocks[block]) return blocks @classmethod def get_amount_from_txt(cls, amount_txt): """ convert text to Decimal """ if (',' in amount_txt) and (amount_txt[-3] == '.'): # '.' = decimal, ',' = tousand amount_txt = amount_txt.replace(',', '.') elif ('.' in amount_txt) and (amount_txt[-3] == ','): # ',' = decimal, '.' = tousand amount_txt = amount_txt.replace('.', '') amount_txt = amount_txt.replace(',', '.') elif ',' in amount_txt: amount_txt = amount_txt.replace(',', '.') return Decimal(amount_txt) @classmethod def qif_read_transactions(cls, transactiondata): """ read transactions from text result: [{ 'split': [{ 'amount': , 'description': 'purpose', 'category': 'name of categroy', },...], 'date': , 'amount': , 'party': 'name of party', 'address': 'address of party', 'checknumber': 'number', 'description': 'purpose', 'state': 'check|edit', 'account': 'name of cashbook', 'category': 'name of category', }, ...] """ result = [] for booktxt in transactiondata.split('^'): if len(booktxt.strip()) == 0: continue booking = {'split': []} for line in booktxt.strip().split('\n'): line_txt = line[1:].strip() if line.startswith('D'): # date booking['date'] = datetime.strptime(line_txt, '%d.%m.%Y').date() elif line.startswith('T'): # total booking['amount'] = cls.get_amount_from_txt(line_txt) elif line.startswith('U'): # total booking['amount'] = cls.get_amount_from_txt(line_txt) elif line.startswith('P'): # party booking['party'] = line_txt elif line.startswith('A'): # address booking['address'] = line_txt elif line.startswith('N'): # address booking['checknumber'] = line_txt elif line.startswith('M'): # memo booking['description'] = line_txt elif line.startswith('C'): # state booking['state'] = { 'X': 'check', '*': 'edit', }.get(line_txt, 'edit') elif line.startswith('L'): # category, account if line_txt.startswith('[') and line_txt.endswith(']'): booking['account'] = line_txt[1:-1] else : booking['category'] = line_txt elif line.startswith('S'): # split: category booking['split'].append({ 'category': line_txt, }) elif line.startswith('E'): # split: memo booking['split'][-1]['description'] = line_txt elif line.startswith('$'): # split: amount booking['split'][-1]['amount'] = cls.get_amount_from_txt(line_txt) elif line.startswith('£'): # split: amount booking['split'][-1]['amount'] = cls.get_amount_from_txt(line_txt) else : raise ValueError('unknown line-code: %s' % (line)) result.append(booking) return result @classmethod def get_category_by_name(cls, catname, cattype): """ find category """ Category = Pool().get('cashbook.category') cat_id = None msg_txt = None categories = Category.search([ ('cattype', '=', cattype), ('rec_name', '=', catname.replace(':', '/')), ]) if len(categories) == 1: cat_id = categories[0].id elif len(categories) == 0: msg_txt = gettext( 'cashbook_dataexchange.mds_import_category_notfound', catname = catname, cattype = cattype, ) else : msg_txt = gettext( 'cashbook_dataexchange.mds_import_many_categories_found', catname1 = catname, catname2 = categories[0].rec_name, cattype = cattype, ) cat_id = categories[0].id return (cat_id, msg_txt) @classmethod def convert_transactions_to_create(cls, transactions, split2edit=True): """ convert read transactions to create-command split2edit: True = split-bokings are 'edit', False = dont change """ to_create = [] msg_list = [] for transaction in transactions: line = {x:transaction[x] for x in [ 'date', 'amount', 'description', 'state', ] if x in transaction.keys()} if len(transaction['split']) > 0: if line['amount'] >= Decimal('0.0'): line['bookingtype'] = 'spin' else : line['bookingtype'] = 'spout' line['amount'] = line['amount'].copy_sign(Decimal('1.0')) else : if line['amount'] >= Decimal('0.0'): line['bookingtype'] = 'in' else : line['bookingtype'] = 'out' line['amount'] = line['amount'].copy_sign(Decimal('1.0')) # store 'account' like 'category' cat_name = transaction.get('category', transaction.get('account', None)) cat_type = 'in' if line['bookingtype'] in ['in', 'spin'] else 'out' if cat_name is not None: (cat_id, msg_txt) = cls.get_category_by_name(cat_name, cat_type) if cat_id is None: msg_list.append(msg_txt) continue else : line['category'] = cat_id if msg_txt is not None: msg_list.append(msg_txt) for x in ['address', 'checknumber']: if x in transaction.keys(): line['description'] = ', '.join([ line.get('description', ''), '%s %s' % ( gettext('cashbook_dataexchange.mds_import_%s' % x), transaction[x] ), ]) split_lines = [] for sp_line in transaction['split']: (cat_id, msg_txt) = cls.get_category_by_name(sp_line['category'], cat_type) if msg_txt is not None: msg_list.append(msg_txt) if cat_id is not None: split_lines.append({ 'amount': sp_line['amount'].copy_sign(Decimal('1.0')), 'description': sp_line.get('description', None), 'category': cat_id, }) if len(split_lines) > 0: line['splitlines'] = [('create', split_lines)] if split2edit == True: if 'splitlines' in line.keys(): line['state'] = 'edit' to_create.append(line) return (to_create, msg_list) @classmethod def qif_read_categories(cls, catdata): """ read categories from text result: { 'in': [{ '': { 'type': 'in|out', 'childs': [...], }, },...], 'out': [{},...], } """ def add_category(catdict, namelst, ctype): """ add category to dict """ if not namelst[0] in catdict.keys(): catdict[namelst[0]] = {'type': ctype, 'childs': {}} if len(namelst) > 1: catdict[namelst[0]]['childs'] = add_category( catdict[namelst[0]]['childs'], namelst[1:], ctype) return catdict categories = {'in': {}, 'out': {}} for cattxt in catdata.split('^'): if len(cattxt.strip()) == 0: continue catname = None cattype = None for line in cattxt.strip().split('\n'): if line.startswith('N'): catname = line[1:].strip().split(':') elif line.startswith('E'): cattype = 'out' elif line.startswith('I'): cattype = 'in' else : raise ValueError('invalid line: %s (%s)' % (line, cattxt)) categories[cattype] = add_category(categories[cattype], catname, cattype) return categories @classmethod def qif_export_category(cls, record): """ export single category as qif """ return '\n'.join([ 'N%(cname)s' % { 'cname': record.rec_name.replace('/', ':'), }, 'E' if record.cattype == 'out' else 'I', '^', ]) # end QifTool