diff --git a/README.rst b/README.rst index a7e7424..d18f680 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,28 @@ Requires Changes ======= +*6.0.5 - 06.06.2023* + +- optimize code + +*6.0.4 - 14.02.2023* + +- fix: test + +*6.0.3 - 13.09.2022* + +- add: import of split-bookings +- updt: transfers + +*6.0.2 - 05.09.2022* + +- category: qif-export +- party, transactions: qif-import/export + +*6.0.1 - 31.08.2022* + +- add: qif - category - import + *6.0.0 - 28.08.2022* - init diff --git a/__init__.py b/__init__.py index 780ef43..eca81a7 100644 --- a/__init__.py +++ b/__init__.py @@ -5,16 +5,25 @@ from trytond.pool import Pool from .category import Category +from .book import Book from .qiftool import QifTool -from .qif_import_wiz import ImportQifWizard, ImportQifWizardStart, ImportQifWizardInfo +from .qif_import_wiz import ( + ImportQifWizard, ImportQifWizardStart, ImportQifWizardInfo) +from .qif_export import QifCategoryExport, QifBookExport + def register(): Pool.register( QifTool, Category, + Book, ImportQifWizardStart, ImportQifWizardInfo, module='cashbook_dataexchange', type_='model') + Pool.register( + QifCategoryExport, + QifBookExport, + module='cashbook_dataexchange', type_='report') Pool.register( ImportQifWizard, module='cashbook_dataexchange', type_='wizard') diff --git a/book.py b/book.py new file mode 100644 index 0000000..392877e --- /dev/null +++ b/book.py @@ -0,0 +1,46 @@ +# -*- 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, PoolMeta + + +class Book(metaclass=PoolMeta): + __name__ = 'cashbook.book' + + @classmethod + def export_as_qif(cls, book): + """ export all transactions as QIF + """ + pool = Pool() + QifTool = pool.get('cashbook_dataexchange.qiftool') + + return QifTool.qif_export_book(book) + + @classmethod + def create_from_qif(cls, book, qifdata): + """ add transactions from QIF-File-content + """ + pool = Pool() + QifTool = pool.get('cashbook_dataexchange.qiftool') + Book2 = pool.get('cashbook.book') + + qif_content = QifTool.split_by_type(qifdata) + if 'Bank' not in qif_content.keys(): + return None + + (to_create, msg_list, fail_cnt) = \ + QifTool.convert_transactions_to_create( + book, + QifTool.qif_read_transactions(qif_content['Bank'])) + if fail_cnt == 0: + Book2.write(*[ + [book], + { + 'lines': [('create', to_create)], + }]) + return [book] + return None + +# end Category diff --git a/category.py b/category.py index 05f31de..3430c44 100644 --- a/category.py +++ b/category.py @@ -3,13 +3,28 @@ # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. -from trytond.transaction import Transaction from trytond.pool import Pool, PoolMeta class Category(metaclass=PoolMeta): __name__ = 'cashbook.category' + @classmethod + def export_as_qif(cls): + """ export all accessible categories as QIF + """ + pool = Pool() + Category2 = pool.get('cashbook.category') + QifTool = pool.get('cashbook_dataexchange.qiftool') + + categories = Category2.search( + [], + order=[('cattype', 'ASC'), ('rec_name', 'ASC')]) + + export = ['!Type:Cat'] + export.extend([QifTool.qif_export_category(x) for x in categories]) + return '\n'.join(export) + @classmethod def create_from_qif(cls, qifdata): """ add categories from QIF-File-content @@ -18,46 +33,12 @@ class Category(metaclass=PoolMeta): QifTool = pool.get('cashbook_dataexchange.qiftool') Category2 = pool.get('cashbook.category') - def get_create(ctype, catdict, parent, do_search): - """ check if category exists, generate create-data - """ - result = [] - for catname in catdict.keys(): - if do_search == True: - c_lst = Category2.search([ - ('cattype', '=', ctype), - ('name', '=', catname), - ('parent', '=', None) if parent is None else ('parent.id', '=', parent.id), - ]) - else : - c_lst = [] - - if len(c_lst) == 0: - cat1 = { - 'cattype': ctype, - 'name': catname, - } - if parent is not None: - cat1['parent'] = parent.id - - if len(catdict[catname]['childs']) > 0: - childs = get_create(ctype, catdict[catname]['childs'], None, False) - if len(childs) > 0: - cat1['childs'] = [('create', childs)] - result.append(cat1) - else : - if len(catdict[catname]['childs']) > 0: - result.extend(get_create(ctype, catdict[catname]['childs'], c_lst[0], True)) - return result - type_data = QifTool.split_by_type(qifdata) - if not 'Cat' in type_data.keys(): + if 'Cat' not in type_data.keys(): return None - cat_tree = QifTool.qif_read_categories(type_data['Cat']) - to_create = [] - for typ1 in ['in', 'out']: - to_create.extend(get_create(typ1, cat_tree[typ1], None, True)) + to_create = QifTool.convert_categories_to_create( + QifTool.qif_read_categories(type_data['Cat'])) return Category2.create(to_create) # end Category diff --git a/locale/de.po b/locale/de.po index 09c0727..a0e184b 100644 --- a/locale/de.po +++ b/locale/de.po @@ -10,6 +10,74 @@ msgctxt "model:ir.message,text:msg_wiz_categories_found" msgid "The following categories are now imported:\n%(categories)s" msgstr "Die folgenden Kategorien werden nun importiert:\n%(categories)s" +msgctxt "model:ir.message,text:msg_wiz_transactions_found" +msgid "The following transactionen are now imported:\nCredit: %(credit)s\nDebit: %(debit)s\nBalance: %(balance)s\nNumber of transactions: %(quantity)s" +msgstr "Die folgenden Transaktionen werden nun importiert:\nEinnahmen: %(credit)s\nAusgaben: %(debit)s\nSaldo: %(balance)s\nAnzahl Transactionen: %(quantity)s" + +msgctxt "model:ir.message,text:msg_wiz_parties_found" +msgid "The following %(numparties)s parties are now imported:" +msgstr "Die folgenden %(numparties)s Parteien werden nun importiert:" + +msgctxt "model:ir.message,text:msg_wiz_no_categories" +msgid "No categories were found in the file." +msgstr "In der Datei wurden keine Kategorien gefunden." + +msgctxt "model:ir.message,text:msg_wiz_no_bank" +msgid "No transactions were found in the file." +msgstr "In der Datei wurden keine Transaktionen gefunden." + +msgctxt "model:ir.message,text:mds_import_category_notfound" +msgid "The category '%(catname)s' was not found." +msgstr "Die Kategorie '%(catname)s' wurde nicht gefunden." + +msgctxt "model:ir.message,text:mds_import_party_notfound" +msgid "The party '%(pname)s' was not found." +msgstr "Die Partei '%(pname)s' wurde nicht gefunden." + +msgctxt "model:ir.message,text:mds_import_many_parties_found" +msgid "For the party '%(pname)s' of the import, several parties were found in the system. Use: '%(pname2)s'" +msgstr "Für die Partei '%(pname)s' des Imports wurden mehrere Parteien im System gefunden. Verwende: '%(pname2)s'" + +msgctxt "model:ir.message,text:mds_import_many_categories_found" +msgid "For the category '%(catname1)s' of the import, several categories were found in the system. Use: '%(catname2)s'" +msgstr "Für die Kategorie '%(catname1)s' des Imports wurden mehrere Kategorien im System gefunden. Verwende: '%(catname2)s'" + +msgctxt "model:ir.message,text:mds_import_many_books_found" +msgid "For the cashbook '%(bookname1)s' of the import, several cashbooks were found in the system. Use: '%(bookname2)s'" +msgstr "Für das Kassenbuch '%(bookname1)s' des Imports wurden mehrere Kassenbücher im System gefunden. Verwende: '%(bookname2)s'" + +msgctxt "model:ir.message,text:mds_import_checknumber" +msgid "Cheque No." +msgstr "Scheck-Nr:" + +msgctxt "model:ir.message,text:mds_import_address" +msgid "Address" +msgstr "Adresse" + +msgctxt "model:ir.message,text:msg_wiz_transactions_error" +msgid "When reading the QIF file, there were the following problems:" +msgstr "Beim Einlesen der QIF-Datei gab es folgende Probleme:" + +msgctxt "model:ir.message,text:mds_import_no_category" +msgid "No category has been assigned for transaction '%(trdata)s'." +msgstr "Für die Transaktion '%(trdata)s' wurde keine Kategorie zugeordnet." + +msgctxt "model:ir.message,text:mds_import_no_account" +msgid "No cashbook has been assigned for transaction '%(trdata)s'." +msgstr "Für die Transaktion '%(trdata)s' wurde kein Kassenbuch zugeordnet." + +msgctxt "model:ir.message,text:mds_import_book_notfound" +msgid "The cashbook '%(bookname)s' was not found." +msgstr "Das Kassenbuch '%(bookname)s' wurde nicht gefunden." + +msgctxt "model:ir.message,text:mds_import_category_not_match" +msgid "The category '%(catname)s' of the split booking does not match the bookingtype '%(bktype)s' (data: '%(data)s')" +msgstr "Die Kategorie '%(catname)s' der Splitbuchung paßt nicht zum Buchungstyp '%(bktype)s' (Daten: '%(data)s')" + +msgctxt "model:ir.message,text:msg_ignore_null_booking" +msgid "Ignore empty booking, no category, amount zero: %(trinfo)s" +msgstr "Ignoriere leere Buchung, keine Kategorie, Betrag Null: %(trinfo)s" + ############# # ir.action # @@ -18,9 +86,13 @@ msgctxt "model:ir.action,name:act_import_qif_wizard" msgid "Import QIF-File" msgstr "QIF-Datei importieren" -msgctxt "model:ir.action,name:msg_wiz_no_categories" -msgid "No categories were found in the file." -msgstr "In der Datei wurden keine Kategorien gefunden." +msgctxt "model:ir.action,name:qif_category_report" +msgid "Export QIF-File" +msgstr "QIF-Datei exportieren" + +msgctxt "model:ir.action,name:qif_transaction_report" +msgid "Export QIF-File" +msgstr "QIF-Datei exportieren" ##################################### @@ -43,8 +115,8 @@ msgid "Cancel" msgstr "Abbruch" msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,showinfo,importf:" -msgid "Import Categories" -msgstr "Kategorien importieren" +msgid "Import Data" +msgstr "Daten importieren" ########################################### @@ -58,6 +130,10 @@ msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,company:" msgid "Company" msgstr "Unternehmen" +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,book:" +msgid "Cashbook" +msgstr "Kassenbuch" + msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,file_:" msgid "QIF-File" msgstr "QIF-Datei" @@ -78,10 +154,14 @@ msgctxt "view:cashbook_dataexchange.qif_imp_wiz.info:" msgid "Information" msgstr "Information" -msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,company:" +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.info,book:" +msgid "Cashbook" +msgstr "Kassenbuch" + +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.info,company:" msgid "Company" msgstr "Unternehmen" -msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,info:" +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.info,info:" msgid "Information" msgstr "Information" diff --git a/locale/en.po b/locale/en.po index df5fa3e..e5e7d31 100644 --- a/locale/en.po +++ b/locale/en.po @@ -2,10 +2,82 @@ msgid "" msgstr "Content-Type: text/plain; charset=utf-8\n" +msgctxt "model:ir.message,text:msg_wiz_categories_found" +msgid "The following categories are now imported:\n%(categories)s" +msgstr "The following categories are now imported:\n%(categories)s" + +msgctxt "model:ir.message,text:msg_wiz_transactions_found" +msgid "The following transactionen are now imported:\nCredit: %(credit)s\nDebit: %(debit)s\nBalance: %(balance)s\nNumber of transactions: %(quantity)s" +msgstr "The following transactionen are now imported:\nCredit: %(credit)s\nDebit: %(debit)s\nBalance: %(balance)s\nNumber of transactions: %(quantity)s" + +msgctxt "model:ir.message,text:msg_wiz_parties_found" +msgid "The following %(numparties)s parties are now imported:" +msgstr "The following %(numparties)s parties are now imported:" + +msgctxt "model:ir.message,text:msg_wiz_no_categories" +msgid "No categories were found in the file." +msgstr "No categories were found in the file." + +msgctxt "model:ir.message,text:msg_wiz_no_bank" +msgid "No transactions were found in the file." +msgstr "No transactions were found in the file." + +msgctxt "model:ir.message,text:mds_import_category_notfound" +msgid "The category '%(catname)s' was not found." +msgstr "The category '%(catname)s' was not found." + +msgctxt "model:ir.message,text:mds_import_party_notfound" +msgid "The party '%(pname)s' was not found." +msgstr "The party '%(pname)s' was not found." + +msgctxt "model:ir.message,text:mds_import_many_parties_found" +msgid "For the party '%(pname)s' of the import, several parties were found in the system. Use: '%(pname2)s'" +msgstr "For the party '%(pname)s' of the import, several parties were found in the system. Use: '%(pname2)s'" + +msgctxt "model:ir.message,text:mds_import_many_categories_found" +msgid "For the category '%(catname1)s' of the import, several categories were found in the system. Use: '%(catname2)s'" +msgstr "For the category '%(catname1)s' of the import, several categories were found in the system. Use: '%(catname2)s'" + +msgctxt "model:ir.message,text:mds_import_many_books_found" +msgid "For the cashbook '%(bookname1)s' of the import, several cashbooks were found in the system. Use: '%(bookname2)s'" +msgstr "For the cashbook '%(bookname1)s' of the import, several cashbooks were found in the system. Use: '%(bookname2)s'" + +msgctxt "model:ir.message,text:mds_import_checknumber" +msgid "Cheque No." +msgstr "Cheque No." + +msgctxt "model:ir.message,text:mds_import_address" +msgid "Address" +msgstr "Address" + +msgctxt "model:ir.message,text:msg_wiz_transactions_error" +msgid "When reading the QIF file, there were the following problems:" +msgstr "When reading the QIF file, there were the following problems:" + +msgctxt "model:ir.message,text:mds_import_no_category" +msgid "No category has been assigned for transaction '%(trdata)s'." +msgstr "No category has been assigned for transaction '%(trdata)s'." + +msgctxt "model:ir.message,text:mds_import_no_account" +msgid "No cashbook has been assigned for transaction '%(trdata)s'." +msgstr "No cashbook has been assigned for transaction '%(trdata)s'." + +msgctxt "model:ir.message,text:mds_import_book_notfound" +msgid "The cashbook '%(bookname)s' was not found." +msgstr "The cashbook '%(bookname)s' was not found." + msgctxt "model:ir.action,name:act_import_qif_wizard" msgid "Import QIF-File" msgstr "Import QIF-File" +msgctxt "model:ir.action,name:qif_category_report" +msgid "Export QIF-File" +msgstr "Export QIF-File" + +msgctxt "model:ir.action,name:qif_transaction_report" +msgid "Export QIF-File" +msgstr "Export QIF-File" + msgctxt "model:cashbook_dataexchange.qif_imp_wiz,name:" msgid "Import QIF-File" msgstr "Import QIF-File" @@ -18,6 +90,14 @@ msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,start,readf:" msgid "Read File" msgstr "Read File" +msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,showinfo,end:" +msgid "Cancel" +msgstr "Cancel" + +msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,showinfo,importf:" +msgid "Import Data" +msgstr "Import Data" + msgctxt "model:cashbook_dataexchange.qif_imp_wiz.start,name:" msgid "Import QIF-File" msgstr "Import QIF-File" @@ -26,7 +106,31 @@ msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,company:" msgid "Company" msgstr "Company" +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,book:" +msgid "Cashbook" +msgstr "Cashbook" + msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,file_:" msgid "QIF-File" msgstr "QIF-File" +msgctxt "help:cashbook_dataexchange.qif_imp_wiz.start,file_:" +msgid "Quicken Interchange Format" +msgstr "Quicken Interchange Format" + +msgctxt "model:cashbook_dataexchange.qif_imp_wiz.info,name:" +msgid "Import QIF-File" +msgstr "Import QIF-File" + +msgctxt "view:cashbook_dataexchange.qif_imp_wiz.info:" +msgid "Information" +msgstr "Information" + +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.info,book:" +msgid "Cashbook" +msgstr "Cashbook" + +msgctxt "field:cashbook_dataexchange.qif_imp_wiz.info,company:" +msgid "Company" +msgstr "Company" + diff --git a/message.xml b/message.xml index fe4a54e..217c4ae 100644 --- a/message.xml +++ b/message.xml @@ -8,10 +8,58 @@ full copyright notices and license terms. --> The following categories are now imported:\n%(categories)s - No categories were found in the file. + + No transactions were found in the file. + + + The following %(numparties)s parties are now imported: + + + The following transactionen are now imported:\nCredit: %(credit)s\nDebit: %(debit)s\nBalance: %(balance)s\nNumber of transactions: %(quantity)s + + + When reading the QIF file, there were the following problems: + + + The cashbook '%(bookname)s' was not found. + + + The category '%(catname)s' was not found. + + + The party '%(pname)s' was not found. + + + For the party '%(pname)s' of the import, several parties were found in the system. Use: '%(pname2)s' + + + For the category '%(catname1)s' of the import, several categories were found in the system. Use: '%(catname2)s' + + + For the cashbook '%(bookname1)s' of the import, several cashbooks were found in the system. Use: '%(bookname2)s' + + + Cheque No. + + + Address + + + No category has been assigned for transaction '%(trdata)s'. + + + No cashbook has been assigned for transaction '%(trdata)s'. + + + The category '%(catname)s' of the split booking does not match the bookingtype '%(bktype)s' (data: '%(data)s') + + + Ignore empty booking, no category, amount zero: %(trinfo)s + + diff --git a/qif_export.py b/qif_export.py new file mode 100644 index 0000000..c8066a6 --- /dev/null +++ b/qif_export.py @@ -0,0 +1,63 @@ +# -*- 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.report import Report +from trytond.pool import Pool +from slugify import slugify + + +class QifCategoryExport(Report): + __name__ = 'cashbook_dataexchange.rep_category' + + @classmethod + def execute(cls, ids, data): + """ filename for export + """ + pool = Pool() + IrDate = pool.get('ir.date') + Category = pool.get('cashbook.category') + + return ( + 'qif', + Category.export_as_qif(), + False, + '%s-categories' % IrDate.today().isoformat().replace('-', '')) + +# end QifCategoryExport + + +class QifBookExport(Report): + __name__ = 'cashbook_dataexchange.rep_book' + + @classmethod + def execute(cls, ids, data): + """ filename for export + """ + pool = Pool() + IrDate = pool.get('ir.date') + Book = pool.get('cashbook.book') + + books = Book.search([('id', '=', data.get('id', -1))]) + if len(books) == 1: + return ( + 'qif', + Book.export_as_qif(books[0]), + False, + slugify('%(date)s-transactions-%(book)s' % { + 'date': IrDate.today().isoformat().replace('-', ''), + 'book': books[0].name, + }, max_length=100, word_boundary=True, save_order=True), + ) + else: + return ( + 'txt', + 'not cashbook found', + False, + '%(date)s-transactions-%(book)s' % { + 'date': IrDate.today().isoformat().replace('-', ''), + 'book': 'not-found', + }) + +# end QifBookExport diff --git a/qif_export.xml b/qif_export.xml new file mode 100644 index 0000000..b9b32f6 --- /dev/null +++ b/qif_export.xml @@ -0,0 +1,37 @@ + + + + + + + Export QIF-File + cashbook.category + cashbook_dataexchange.rep_category + cashbook_dataexchange/report/export.fods + ods + + + + form_action + cashbook.category,-1 + + + + + Export QIF-File + cashbook.book + cashbook_dataexchange.rep_book + cashbook_dataexchange/report/export.fods + ods + + + + form_action + cashbook.book,-1 + + + + + diff --git a/qif_import_wiz.py b/qif_import_wiz.py index 0687533..d79f927 100644 --- a/qif_import_wiz.py +++ b/qif_import_wiz.py @@ -7,18 +7,26 @@ from trytond.transaction import Transaction from trytond.pool import Pool from trytond.model import ModelView, fields from trytond.wizard import Wizard, StateTransition, StateView, Button -from trytond.transaction import Transaction from trytond.i18n import gettext +from trytond.pyson import Eval, Bool +from trytond.report import Report class ImportQifWizardStart(ModelView): 'Import QIF-File' __name__ = 'cashbook_dataexchange.qif_imp_wiz.start' - company = fields.Many2One(model_name='company.company', + company = fields.Many2One( + model_name='company.company', string="Company", required=True, states={'invisible': True}) - file_ = fields.Binary(string="QIF-File", required=True, + book = fields.Many2One( + string='Cashbook', readonly=True, + model_name='cashbook.book', + states={ + 'invisible': ~Bool(Eval('book'))}) + file_ = fields.Binary( + string="QIF-File", required=True, help='Quicken Interchange Format') @classmethod @@ -32,8 +40,15 @@ class ImportQifWizardInfo(ModelView): 'Import QIF-File' __name__ = 'cashbook_dataexchange.qif_imp_wiz.info' - company = fields.Many2One(model_name='company.company', - string="Company", required=True, + company = fields.Many2One( + model_name='company.company', string="Company", + required=True, states={'invisible': True}) + book = fields.Many2One( + string='Cash Book', readonly=True, model_name='cashbook.book', + states={ + 'invisible': ~Bool(Eval('book'))}) + allowimport = fields.Boolean( + string='Import Enabled', states={'invisible': True}) info = fields.Text(string='Information', readonly=True) @@ -45,28 +60,49 @@ class ImportQifWizard(Wizard): __name__ = 'cashbook_dataexchange.qif_imp_wiz' start_state = 'start' - start = StateView(model_name='cashbook_dataexchange.qif_imp_wiz.start', \ - view='cashbook_dataexchange.qif_imp_wiz_start_form', \ + start = StateView( + model_name='cashbook_dataexchange.qif_imp_wiz.start', + view='cashbook_dataexchange.qif_imp_wiz_start_form', buttons=[ Button(string='Cancel', state='end', icon='tryton-cancel'), - Button(string='Read File', state='readf', icon='tryton-forward', default=True), - ]) - showinfo = StateView(model_name='cashbook_dataexchange.qif_imp_wiz.info', \ - view='cashbook_dataexchange.qif_imp_wiz_info_form', \ + Button( + string='Read File', state='readf', + icon='tryton-forward', default=True)]) + showinfo = StateView( + model_name='cashbook_dataexchange.qif_imp_wiz.info', + view='cashbook_dataexchange.qif_imp_wiz_info_form', buttons=[ Button(string='Cancel', state='end', icon='tryton-cancel'), - Button(string='Import Categories', state='importf', icon='tryton-import', default=True), - ]) + Button( + string='Import Data', state='importf', + icon='tryton-import', default=True, + states={'readonly': ~Eval('allowimport', False)})]) readf = StateTransition() importf = StateTransition() + def default_start(self, fields): + """ show book, company + """ + context = Transaction().context + + values = { + 'company': Transaction().context.get('company'), + 'book': None} + + model = context.get('active_model', '') + if model == 'cashbook.book': + values['book'] = context.get('active_id', None) + return values + def default_showinfo(self, fields): """ show import-info """ values = { 'company': self.start.company.id, 'info': getattr(self.showinfo, 'info', None), + 'book': getattr(getattr(self.start, 'book', None), 'id', None), + 'allowimport': getattr(self.showinfo, 'allowimport', False), } return values @@ -74,43 +110,127 @@ class ImportQifWizard(Wizard): """ read file, show number of objects """ pool = Pool() - QitTool = pool.get('cashbook_dataexchange.qiftool') + QifTool = pool.get('cashbook_dataexchange.qiftool') + Category = pool.get('cashbook.category') model = Transaction().context.get('active_model', '') file_content = None if isinstance(self.start.file_, bytes): file_content = self.start.file_.decode('utf8') + self.showinfo.allowimport = False if model == 'cashbook.category': - def get_catlist(catlist, parent_name=None): + def get_catlist(record, cattype, parent_name=None): """ generate list of categories """ names = [] - for name1 in catlist.keys(): - name_lst = [] - if parent_name: - name_lst.append(parent_name) - name_lst.append(name1) - current_name = '/'.join(name_lst) - names.append(current_name) - names.extend(get_catlist(catlist[name1]['childs'], current_name)) + + if record['cattype'] != cattype: + return [] + + if 'parent' in record.keys(): + parent_name = Category(record['parent']).rec_name + + name_lst = [] + if parent_name: + name_lst.append(parent_name) + name_lst.append(record['name']) + current_name = '/'.join(name_lst) + names.append(current_name) + + if 'childs' in record.keys(): + # record['childs']: [('create', [{}, ...]))] + for x in record['childs'][0][1]: + names.extend(get_catlist(x, cattype, current_name)) return names # read file content, extract categories - qif_content = QitTool.split_by_type(file_content) + qif_content = QifTool.split_by_type(file_content) if 'Cat' in qif_content.keys(): - categories = QitTool.qif_read_categories(qif_content['Cat']) + to_create = QifTool.convert_categories_to_create( + QifTool.qif_read_categories(qif_content['Cat'])) + + in_categories = [] + out_categories = [] + for x in to_create: + in_categories.extend(get_catlist(x, 'in')) + out_categories.extend(get_catlist(x, 'out')) + self.showinfo.info = gettext( 'cashbook_dataexchange.msg_wiz_categories_found', - categories = '\n'.join( + categories='\n'.join( [''] + - ['%s (in)' % x for x in get_catlist(categories['in'], None)]+ + ['%s (in)' % x for x in in_categories] + [''] + - ['%s (out)' % x for x in get_catlist(categories['out'], None)] - ) - ) - else : - self.showinfo.info = gettext('cashbook_dataexchange.msg_wiz_no_categories') + ['%s (out)' % x for x in out_categories] + )) + if len(to_create) > 0: + self.showinfo.allowimport = True + else: + self.showinfo.info = gettext( + 'cashbook_dataexchange.msg_wiz_no_categories') + elif model == 'party.party': + # read file content, extract parties + qif_content = QifTool.split_by_type(file_content) + if 'Bank' in qif_content.keys(): + to_create = QifTool.convert_parties_to_create( + QifTool.qif_read_transactions(qif_content['Bank'])) + self.showinfo.info = gettext( + 'cashbook_dataexchange.msg_wiz_parties_found', + numparties=len(to_create), + ) + '\n\n' + '\n'.join([x['name'] for x in to_create]) + if len(to_create) > 0: + self.showinfo.allowimport = True + else: + self.showinfo.info = gettext( + 'cashbook_dataexchange.msg_wiz_no_bank') + elif model == 'cashbook.book': + info_lst = [] + # read file content, extract categories + qif_content = QifTool.split_by_type(file_content) + if 'Bank' in qif_content.keys(): + (to_create, msg_list, fail_cnt) = \ + QifTool.convert_transactions_to_create( + self.start.book, + QifTool.qif_read_transactions(qif_content['Bank'])) + if len(msg_list) > 0: + info_lst.append(gettext( + 'cashbook_dataexchange.msg_wiz_transactions_error')) + info_lst.append('') + + short_lst = [] + for x in msg_list: + if x not in short_lst: + short_lst.append(x) + info_lst.extend(short_lst) + info_lst.append('') + + # count + if fail_cnt == 0: + debit = sum([ + x['amount'] for x in to_create + if x['bookingtype'] in ['out', 'mvout', 'spout']]) + credit = sum([ + x['amount'] for x in to_create + if x['bookingtype'] in ['in', 'mvin', 'spin']]) + balance = credit - debit + + if len(msg_list) > 0: + msg_list.append('') + info_lst.append(gettext( + 'cashbook_dataexchange.msg_wiz_transactions_found', + quantity=len(to_create), + balance=Report.format_currency( + balance, None, self.start.book.currency), + credit=Report.format_currency( + credit, None, self.start.book.currency), + debit=Report.format_currency( + debit, None, self.start.book.currency))) + self.showinfo.allowimport = True + else: + info_lst.append(gettext( + 'cashbook_dataexchange.msg_wiz_no_bank')) + self.showinfo.info = '\n'.join(info_lst) return 'showinfo' @@ -119,6 +239,10 @@ class ImportQifWizard(Wizard): """ pool = Pool() Category = pool.get('cashbook.category') + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Party = pool.get('party.party') + QifTool = pool.get('cashbook_dataexchange.qiftool') model = Transaction().context.get('active_model', '') file_content = None @@ -127,9 +251,22 @@ class ImportQifWizard(Wizard): if model == 'cashbook.category': if file_content: - records = Category.create_from_qif(file_content) - + Category.create_from_qif(file_content) + elif model == 'cashbook.book': + if file_content: + Book.create_from_qif(self.showinfo.book, file_content) + lines = Line.search([ + ('cashbook.id', '=', self.showinfo.book.id), + ('state', '=', 'edit')]) + if len(lines) > 0: + Line.wfcheck(lines) + elif model == 'party.party': + qif_content = QifTool.split_by_type(file_content) + if 'Bank' in qif_content.keys(): + to_create = QifTool.convert_parties_to_create( + QifTool.qif_read_transactions(qif_content['Bank']) + ) + Party.create(to_create) return 'end' # end ImportQifWizard - diff --git a/qif_import_wiz.xml b/qif_import_wiz.xml index f354c18..ea49a0e 100644 --- a/qif_import_wiz.xml +++ b/qif_import_wiz.xml @@ -29,5 +29,19 @@ full copyright notices and license terms. --> + + + form_action + cashbook.book,-1 + + + + + + form_action + party.party,-1 + + + diff --git a/qiftool.py b/qiftool.py index 0743f3b..b40ca40 100644 --- a/qiftool.py +++ b/qiftool.py @@ -5,6 +5,10 @@ from trytond.pool import Pool from trytond.model import Model +from trytond.i18n import gettext +from trytond.report import Report +from decimal import Decimal +from datetime import datetime class QifTool(Model): @@ -22,11 +26,11 @@ class QifTool(Model): for line in lines: if line.startswith('!Type:'): current_type = line[len('!Type:'):].strip() - else : + else: if current_type is None: continue - if not current_type in blocks.keys(): + if current_type not in blocks.keys(): blocks[current_type] = [] blocks[current_type].append(line.strip()) @@ -34,6 +38,542 @@ class QifTool(Model): 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, account + if line_txt.startswith('[') and line_txt.endswith(']'): + booking['split'].append({ + 'account': line_txt[1:-1], + }) + else: + 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 qif_export_book(cls, book): + """ export book + """ + result = ['!Type:Bank'] + + def get_amount_by_bookingstate(amount, line): + """ get amount with sign + """ + if line.bookingtype in ['in', 'spin', 'mvin']: + return amount + elif line.bookingtype in ['out', 'spout', 'mvout']: + return amount * Decimal('-1.0') + else: + raise ValueError('invalid bookingtype: %s' % line.bookingtype) + + for line in book.lines: + # date + result.append('D%(date)s' % { + 'date': Report.format_date(line.date, None), + }) + # total + result.append('T%(total)s' % { + 'total': Report.format_number( + get_amount_by_bookingstate(line.amount, line), + None, + digits=book.currency.digits), + }) + # state + result.append('C%(state)s' % { + 'state': 'X' if line.state in ['check', 'done'] else '*', + }) + # party + if line.party: + result.append('P%(party)s' % { + 'party': line.party.rec_name, + }) + # address + p_address = line.party.address_get() + if p_address: + if len(p_address.full_address.strip()) > 0: + result.append('A%(address)s' % { + 'address': p_address.full_address.replace( + '\n', ', ').strip()}) + # category + if line.category: + result.append('L%(category)s' % { + 'category': line.category.rec_name.replace('/', ':'), + }) + # account + if line.booktransf: + result.append('L[%(account)s]' % { + 'account': line.booktransf.name, + }) + # description + if line.description: + result.append('M%(memo)s' % { + 'memo': line.description.replace('\n', '; ') + }) + + # split-booking + for splitline in line.splitlines: + result.append('S%(category)s' % { + 'category': splitline.category.rec_name.replace('/', ':'), + }) + if splitline.description: + result.append('E%(memo)s' % { + 'memo': splitline.description.replace('\n', '; ') + }) + result.append('$%(total)s' % { + 'total': Report.format_number( + get_amount_by_bookingstate(splitline.amount, line), + None, + digits=book.currency.digits), + }) + result.append('^') + return '\n'.join(result) + + @classmethod + def get_party_by_name(cls, partyname): + """ find party + """ + Party = Pool().get('party.party') + + party_id = None + msg_txt = None + + parties = Party.search([('rec_name', 'ilike', '%%%s%%' % partyname)]) + if len(parties) == 0: + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_party_notfound', + pname=partyname) + elif len(parties) == 1: + party_id = parties[0].id + else: + party_id = parties[0].id + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_many_parties_found', + pname=partyname, + pname2=parties[0].rec_name) + return (party_id, msg_txt) + + @classmethod + def get_account_by_name(cls, book, account_name): + """ find cashbook + """ + Book = Pool().get('cashbook.book') + + book_obj = None + msg_txt = None + books = Book.search([ + ('name', '=', account_name), + ('owner.id', '=', book.owner.id), + ('id', '!=', book.id), + ]) + if len(books) == 1: + book_obj = books[0] + elif len(books) == 0: + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_book_notfound', + bookname=account_name, + ) + else: + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_many_books_found', + bookname1=account_name, + bookname2=books[0].rec_name, + ) + book_obj = books[0] + return (book_obj, msg_txt) + + @classmethod + def get_category_by_name(cls, company, catname): + """ find category + """ + Category = Pool().get('cashbook.category') + + cat_obj = None + msg_txt = None + categories = Category.search([ + ('rec_name', '=', catname.replace(':', '/')), + ('company.id', '=', company.id), + ]) + if len(categories) == 1: + cat_obj = categories[0] + elif len(categories) == 0: + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_category_notfound', + catname=catname, + ) + else: + msg_txt = gettext( + 'cashbook_dataexchange.mds_import_many_categories_found', + catname1=catname, + catname2='%(name)s [%(type)s]' % { + 'name': categories[0].rec_name, + 'type': categories[0].cattype, + }, + ) + cat_obj = categories[0] + return (cat_obj, msg_txt) + + @classmethod + def convert_categories_to_create(cls, cat_tree): + """ cat_tree: result from cls.qif_read_categories() + """ + Category = Pool().get('cashbook.category') + + def get_create(ctype, catdict, parent, do_search): + """ check if category exists, generate create-data + """ + result = [] + for catname in catdict.keys(): + if do_search is True: + c_lst = Category.search([ + ('cattype', '=', ctype), + ('name', '=', catname), + ('parent', '=', None) + if parent is None else ('parent.id', '=', parent.id)]) + else: + c_lst = [] + + if len(c_lst) == 0: + cat1 = { + 'cattype': ctype, + 'name': catname, + } + if parent is not None: + cat1['parent'] = parent.id + + if len(catdict[catname]['childs']) > 0: + childs = get_create( + ctype, catdict[catname]['childs'], None, False) + if len(childs) > 0: + cat1['childs'] = [('create', childs)] + result.append(cat1) + else: + if len(catdict[catname]['childs']) > 0: + result.extend(get_create( + ctype, catdict[catname]['childs'], c_lst[0], True)) + return result + to_create = [] + for typ1 in ['in', 'out']: + to_create.extend(get_create(typ1, cat_tree[typ1], None, True)) + return to_create + + @classmethod + def convert_parties_to_create(cls, transactions): + """ extract party from transaction, check if exist, + create 'to_create' + """ + Party = Pool().get('party.party') + + to_create = [] + party_cache = [] + for transaction in transactions: + if 'party' in transaction.keys(): + if transaction['party'] in party_cache: + continue + + party_cache.append(transaction['party']) + if Party.search_count([ + ('rec_name', 'ilike', '%%%(pname)s%%' % { + 'pname': transaction['party'], + })]) == 0: + to_create.append({ + 'name': transaction['party'], + 'addresses': [('create', [{ + 'street': transaction.get('address', None), + }])], + }) + return to_create + + @classmethod + def check_counter_transaction(cls, book, line): + """ check if planned transaction was already inserted by + import to target-cashbook + """ + Line = Pool().get('cashbook.line') + + if Line.search_count([ + ('cashbook.id', '=', book.id), + ('booktransf.id', '=', line['booktransf']), + ('date', '=', line['date']), + # ('description', '=', line['description']), + ('amount', '=', line['amount']), + ('bookingtype', '=', line['bookingtype']), + ]) > 0: + return True + else: + return False + + @classmethod + def get_category_account(cls, book, transaction): + """ get category or account + """ + cat_name = transaction.get('category', None) + account_name = transaction.get('account', None) + msg_list = [] + fail_cnt = 0 + category = account = None + if cat_name is not None: + (category, msg_txt) = cls.get_category_by_name( + book.company, cat_name) + if category is None: + msg_list.append(msg_txt) + fail_cnt += 1 + elif account_name is not None: + (account, msg_txt) = cls.get_account_by_name(book, account_name) + if account is None: + msg_list.append(msg_txt) + fail_cnt += 1 + return (category, account, msg_list, fail_cnt) + + @classmethod + def convert_transactions_to_create( + cls, book, transactions, split2edit=True): + """ convert read transactions to create-command + split2edit: True = split-bookings are 'edit', False = dont change + """ + def updt_description(descr_txt): + """ repair line breaks + """ + if descr_txt is None: + return None + return descr_txt.replace('\\n', '\n') + + to_create = [] + msg_list = [] + fail_cnt = 0 + for transaction in transactions: + line = { + x: transaction[x] + for x in ['date', 'amount', 'description', 'state'] + if x in transaction.keys()} + + if 'description' in line.keys(): + line['description'] = updt_description(line['description']) + + (category, account, msg_lst2, fail_cnt2) = \ + cls.get_category_account(book, transaction) + msg_list.extend(msg_lst2) + if fail_cnt2 > 0: + fail_cnt += fail_cnt2 + continue + + if category: + cat_type = category.cattype + line['category'] = category.id + + if cat_type == 'out': + # amount of usually out-transaction are negative in QIF, + # if its a return-transaction it should be positive + line['amount'] = line['amount'].copy_negate() + + line['bookingtype'] = cat_type + if len(transaction['split']) > 0: + line['bookingtype'] = { + 'in': 'spin', + 'out': 'spout', + }[cat_type] + elif account: + if line['amount'] < Decimal('0.0'): + line['bookingtype'] = 'mvout' + line['amount'] = line['amount'].copy_negate() + else: + line['bookingtype'] = 'mvin' + + line['booktransf'] = account.id + descr_lst = [transaction.get('party', '-')] + if 'description' in line.keys(): + descr_lst.append(line['description']) + if 'party' in transaction.keys(): + del transaction['party'] + line['description'] = '; '.join(descr_lst) + line['state'] = 'edit' + if cls.check_counter_transaction(book, line) is True: + # counter-transaction already exists + continue + else: + # transaction: no category, no account - ignore? + if line.get('amount', Decimal('0.0')) == Decimal('0.0'): + # no amount --> ignore! + tr_info = {'trdate': '-', 'amount': '-'} + if 'date' in transaction.keys(): + tr_info['trdate'] = Report.format_date( + transaction['date'], None) + if 'amount' in transaction.keys(): + tr_info['amount'] = Report.format_currency( + transaction['amount'], + None, + book.currency) + tr_info['descr'] = transaction.get('description', '-') + msg_list.append(gettext( + 'cashbook_dataexchange.msg_ignore_null_booking', + trinfo='%(trdate)s, %(amount)s, %(descr)s' % tr_info, + )) + continue + + # party + if 'party' in transaction.keys(): + (party_id, msg_txt) = cls.get_party_by_name( + transaction['party']) + if party_id is not None: + line['party'] = party_id + else: + fail_cnt += 1 + 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']: + (category, account, msg_lst2, fail_cnt2) = \ + cls.get_category_account(book, sp_line) + msg_list.extend(msg_lst2) + if fail_cnt2 > 0: + fail_cnt += fail_cnt2 + continue + + split_line = { + 'amount': sp_line['amount'] + if line['bookingtype'].endswith('in') + else sp_line['amount'].copy_negate(), + 'description': updt_description( + sp_line.get('description', None)), + } + + if category: + # category match to bookingtype? + if ((category.cattype == 'in') and + line['bookingtype'].endswith('out')) or \ + ((category.cattype == 'out') and + line['bookingtype'].endswith('in')): + msg_list.append(gettext( + 'cashbook_dataexchange.' + + 'mds_import_category_not_match', + catname='%s [%s]' % ( + category.rec_name, category.cattype), + bktype=line['bookingtype'], + data=str(transaction))) + fail_cnt += 1 + continue + split_line['splittype'] = 'cat' + split_line['category'] = category.id + elif account: + split_line['splittype'] = 'tr' + split_line['booktransf'] = account.id + else: + continue + + split_lines.append(split_line) + + if len(split_lines) > 0: + line['splitlines'] = [('create', split_lines)] + + if split2edit is True: + if 'splitlines' in line.keys(): + line['state'] = 'edit' + + # check data + if line['bookingtype'] in ['in', 'out']: + if line.get('category', None) is None: + msg_list.append(gettext( + 'cashbook_dataexchange.mds_import_no_category', + trdata=str(transaction))) + fail_cnt += 1 + elif line['bookingtype'] in ['mvin', 'mvout']: + if line.get('booktransf', None) is None: + msg_list.append(gettext( + 'cashbook_dataexchange.mds_import_no_account', + trdata=str(transaction))) + fail_cnt += 1 + + to_create.append(line) + return (to_create, msg_list, fail_cnt) + @classmethod def qif_read_categories(cls, catdata): """ read categories from text @@ -73,9 +613,22 @@ class QifTool(Model): cattype = 'out' elif line.startswith('I'): cattype = 'in' - else : + else: raise ValueError('invalid line: %s (%s)' % (line, cattxt)) - categories[cattype] = add_category(categories[cattype], catname, cattype) + 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 diff --git a/report/exort.fods b/report/exort.fods new file mode 100644 index 0000000..86a7924 --- /dev/null +++ b/report/exort.fods @@ -0,0 +1,240 @@ + + + + 2017-10-20T23:41:04.964000000LibreOffice/6.0.7.3$Linux_X86_64 LibreOffice_project/00m0$Build-3 + + + 0 + 0 + 2257 + 451 + + + view1 + + + 0 + 0 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 100 + 60 + true + false + + + Sheet1 + 1828 + 0 + 100 + 60 + false + true + true + true + 12632256 + true + true + true + true + false + false + false + 1270 + 1270 + 1 + 1 + true + false + + + + + 7 + true + false + false + 0 + true + owH+/0hQX0xhc2VySmV0X01GUF9NNDI2ZmRuX0IyNUM3Ml8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQ1VQUzpIUF9MYXNlckpldF9NRlBfTTQyNmZkbl9CMgAWAAMAxAAAAAAAAAAEAAhSAAAEdAAASm9iRGF0YSAxCnByaW50ZXI9SFBfTGFzZXJKZXRfTUZQX000MjZmZG5fQjI1QzcyXwpvcmllbnRhdGlvbj1Qb3J0cmFpdApjb3BpZXM9MQpjb2xsYXRlPWZhbHNlCm1hcmdpbmRhanVzdG1lbnQ9MCwwLDAsMApjb2xvcmRlcHRoPTI0CnBzbGV2ZWw9MApwZGZkZXZpY2U9MQpjb2xvcmRldmljZT0wClBQRENvbnRleERhdGEKUGFnZVNpemU6QTQAABIAQ09NUEFUX0RVUExFWF9NT0RFDwBEdXBsZXhNb2RlOjpPZmY= + HP_LaserJet_MFP_M426fdn_B25C72_ + true + 3 + true + false + true + true + 12632256 + true + true + true + false + true + false + true + 1 + false + 1270 + 1270 + false + 1 + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + Page 1 + + + + + + + ???(???) + + + 00.00.0000, 00:00:00 + + + + + Page 1/ 99 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup.py b/setup.py index 1eff6ad..8f8725e 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ """ # Always prefer setuptools over distutils -from setuptools import setup, find_packages +from setuptools import setup # To use a consistent encoding from codecs import open from os import path @@ -36,13 +36,13 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f: l2 = i.strip().split(';') if len(l2) < 4: continue - modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]} + modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]} # tryton-version major_version = 6 minor_version = 0 -requires = [] +requires = ['python-slugify'] for dep in info.get('depends', []): if not re.match(r'(ir|res|webdav)(\W|$)', dep): if dep in modversion.keys(): @@ -51,55 +51,59 @@ for dep in info.get('depends', []): prefix = modversion[dep]['prefix'] if len(modversion[dep]['max']) > 0: - requires.append('%s_%s >= %s, <= %s' % - (prefix, dep, modversion[dep]['min'], modversion[dep]['max'])) - else : - requires.append('%s_%s >= %s' % - (prefix, dep, modversion[dep]['min'])) - else : - requires.append('%s_%s >= %s.%s, < %s.%s' % - ('trytond', dep, major_version, minor_version, + requires.append('%s_%s >= %s, <= %s' % ( + prefix, dep, modversion[dep]['min'], + modversion[dep]['max'])) + else: + requires.append('%s_%s >= %s' % ( + prefix, dep, modversion[dep]['min'])) + else: + requires.append('%s_%s >= %s.%s, < %s.%s' % ( + 'trytond', dep, major_version, minor_version, major_version, minor_version + 1)) -requires.append('trytond >= %s.%s, < %s.%s' % - (major_version, minor_version, major_version, minor_version + 1)) +requires.append('trytond >= %s.%s, < %s.%s' % ( + major_version, minor_version, major_version, minor_version + 1)) -setup(name='%s_%s' % (PREFIX, MODULE), +setup( + name='%s_%s' % (PREFIX, MODULE), version=info.get('version', '0.0.1'), description='Tryton module to add import/export to cashbook.', long_description=long_description, + long_description_content_type='text/x-rst', url='https://www.m-ds.de/', + download_url='https://scmdev.m-ds.de/Tryton/Extra/cashbook_dataexchange', author='martin-data services', author_email='service@m-ds.de', license='GPL-3', classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Plugins', - 'Framework :: Tryton', - 'Intended Audience :: Developers', - 'Intended Audience :: Customer Service', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Financial and Insurance Industry', - 'Topic :: Office/Business', - 'Topic :: Office/Business :: Financial :: Accounting', - 'Natural Language :: German', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Plugins', + 'Framework :: Tryton', + 'Intended Audience :: Developers', + 'Intended Audience :: Customer Service', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Financial and Insurance Industry', + 'Topic :: Office/Business', + 'Topic :: Office/Business :: Financial :: Accounting', + 'Natural Language :: German', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], - keywords='tryton cashbook import export', package_dir={'trytond.modules.%s' % MODULE: '.'}, packages=[ 'trytond.modules.%s' % MODULE, ], package_data={ - 'trytond.modules.%s' % MODULE: (info.get('xml', []) - + ['tryton.cfg', 'locale/*.po', 'tests/*.py', - 'view/*.xml', 'versiondep.txt', 'README.rst']), + 'trytond.modules.%s' % MODULE: (info.get('xml', []) + [ + 'tryton.cfg', 'locale/*.po', 'tests/*.py', + 'report/*.fods', 'view/*.xml', + 'versiondep.txt', 'README.rst']), }, - install_requires=requires, zip_safe=False, entry_points=""" diff --git a/tests/__init__.py b/tests/__init__.py index 19ff9ad..9ca41fe 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,23 +1,18 @@ -# This file is part of Tryton. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. +# -*- 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. import trytond.tests.test_tryton import unittest -from trytond.modules.cashbook_dataexchange.tests.test_category import CategoryTestCase +from .test_module import CashbookExchangeTestCase __all__ = ['suite'] -class CashbookExchangeTestCase(\ - CategoryTestCase,\ - ): - 'Test cashbook exchange module' - module = 'cashbook_dataexchange' - -# end CashbookExchangeTestCase - def suite(): suite = trytond.tests.test_tryton.suite() - suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CashbookExchangeTestCase)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase( + CashbookExchangeTestCase)) return suite diff --git a/tests/category.py b/tests/category.py new file mode 100644 index 0000000..fdbd2a6 --- /dev/null +++ b/tests/category.py @@ -0,0 +1,507 @@ +# -*- 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 datetime import date +from decimal import Decimal +from trytond.tests.test_tryton import with_transaction +from trytond.pool import Pool +from trytond.transaction import Transaction +from .qifdata import qif_types + + +class CategoryTestCase(object): + 'Test cashbook categoy module' + module = 'cashbook_dataexchange' + + @with_transaction() + def test_wiz_import_category(self): + """ create categories by run wizard + """ + pool = Pool() + Category = pool.get('cashbook.category') + ImportWiz = pool.get( + 'cashbook_dataexchange.qif_imp_wiz', type='wizard') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + 'active_model': 'cashbook.category'}): + (sess_id, start_state, end_state) = ImportWiz.create() + w_obj = ImportWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + # run start + result = ImportWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + + r1 = {} + r1['file_'] = qif_types.encode('utf8') + r1['company'] = company.id + w_obj.start.file_ = r1['file_'] + w_obj.start.company = company.id + + result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') + + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + self.assertEqual( + result['view']['defaults']['info'], + """The following categories are now imported:\n +Gehalt (in) +Gehalt/Zulagen (in) + +Telekommunikation (out) +Telekommunikation/Online-Dienste (out) +Telekommunikation/Telefon (out) +Telekommunikation/Telefon/Test1 (out) +Telekommunikation/Fernsehen (out) +Telefon (out) +Telefon/Telco1-Tablett (out) +Telefon/Telco2-Handy (out) +Telefon/Telco3 (out) +Fernsehen (out) +Fernsehen/TV-Company (out) +Fernsehen/GEZ (out) +Lebensmittel (out)""") + + r1 = {'company': company.id} + result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') + self.assertEqual(list(result.keys()), []) + + ImportWiz.delete(sess_id) + + records = Category.search([], order=[('rec_name', 'ASC')]) + self.assertEqual(len(records), 15) + + self.assertEqual(records[0].rec_name, 'Fernsehen') + self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') + self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') + self.assertEqual(records[3].rec_name, 'Gehalt') + self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') + self.assertEqual(records[5].rec_name, 'Lebensmittel') + self.assertEqual(records[6].rec_name, 'Telefon') + self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') + self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') + self.assertEqual(records[9].rec_name, 'Telefon/Telco3') + self.assertEqual(records[10].rec_name, 'Telekommunikation') + self.assertEqual( + records[11].rec_name, 'Telekommunikation/Fernsehen') + self.assertEqual( + records[12].rec_name, 'Telekommunikation/Online-Dienste') + self.assertEqual( + records[13].rec_name, 'Telekommunikation/Telefon') + self.assertEqual( + records[14].rec_name, 'Telekommunikation/Telefon/Test1') + + @with_transaction() + def test_category_create_by_qif_emptydb(self): + """ create categories by import a qif-file + """ + pool = Pool() + Category = pool.get('cashbook.category') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id}): + records = Category.create_from_qif(qif_types) + + records = Category.search([], order=[('rec_name', 'ASC')]) + self.assertEqual(len(records), 15) + + self.assertEqual(records[0].rec_name, 'Fernsehen') + self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') + self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') + self.assertEqual(records[3].rec_name, 'Gehalt') + self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') + self.assertEqual(records[5].rec_name, 'Lebensmittel') + self.assertEqual(records[6].rec_name, 'Telefon') + self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') + self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') + self.assertEqual(records[9].rec_name, 'Telefon/Telco3') + self.assertEqual(records[10].rec_name, 'Telekommunikation') + self.assertEqual( + records[11].rec_name, 'Telekommunikation/Fernsehen') + self.assertEqual( + records[12].rec_name, 'Telekommunikation/Online-Dienste') + self.assertEqual( + records[13].rec_name, 'Telekommunikation/Telefon') + self.assertEqual( + records[14].rec_name, 'Telekommunikation/Telefon/Test1') + + result = Category.export_as_qif() + self.assertEqual(result, """!Type:Cat +NFernsehen +E +^ +NFernsehen:GEZ +E +^ +NFernsehen:TV-Company +E +^ +NLebensmittel +E +^ +NTelefon +E +^ +NTelefon:Telco1-Tablett +E +^ +NTelefon:Telco2-Handy +E +^ +NTelefon:Telco3 +E +^ +NTelekommunikation +E +^ +NTelekommunikation:Fernsehen +E +^ +NTelekommunikation:Online-Dienste +E +^ +NTelekommunikation:Telefon +E +^ +NTelekommunikation:Telefon:Test1 +E +^ +NGehalt +I +^ +NGehalt:Zulagen +I +^""") + + @with_transaction() + def test_category_create_by_qif_existing_categories(self): + """ create categories by import a qif-file, + some categories exists already + """ + pool = Pool() + Category = pool.get('cashbook.category') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id}): + cat1, = Category.create([{ + 'name': 'Telekommunikation', + 'cattype': 'out', + 'childs': [('create', [{ + 'cattype': 'out', + 'name': 'Telefon', + }])], + }]) + + records = Category.search([]) + self.assertEqual(len(records), 2) + self.assertEqual(records[0].rec_name, 'Telekommunikation') + self.assertEqual(records[1].rec_name, 'Telekommunikation/Telefon') + + Category.create_from_qif(qif_types) + + records = Category.search([], order=[('rec_name', 'ASC')]) + self.assertEqual(len(records), 15) + + self.assertEqual(records[0].rec_name, 'Fernsehen') + self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') + self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') + self.assertEqual(records[3].rec_name, 'Gehalt') + self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') + self.assertEqual(records[5].rec_name, 'Lebensmittel') + self.assertEqual(records[6].rec_name, 'Telefon') + self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') + self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') + self.assertEqual(records[9].rec_name, 'Telefon/Telco3') + self.assertEqual(records[10].rec_name, 'Telekommunikation') + self.assertEqual( + records[11].rec_name, 'Telekommunikation/Fernsehen') + self.assertEqual( + records[12].rec_name, 'Telekommunikation/Online-Dienste') + self.assertEqual( + records[13].rec_name, 'Telekommunikation/Telefon') + self.assertEqual( + records[14].rec_name, 'Telekommunikation/Telefon/Test1') + + @with_transaction() + def test_qiftool_split_types(self): + """ split file-content by types + """ + QifTool = Pool().get('cashbook_dataexchange.qiftool') + + result = QifTool.split_by_type(qif_types) + self.assertEqual(len(result.keys()), 2) + self.assertEqual( + result['Cat'], + 'NGehalt\nI\n^\nNGehalt:Zulagen\nI\n^\nNTelekommunikation' + + '\nE\n^\nNTelekommunikation:Online-Dienste\n' + + 'E\n^\nNTelekommunikation:Telefon\nE\n^\nN' + + 'Telekommunikation:Telefon:Test1\n' + + 'E\n^\nNTelefon:Telco1-Tablett\n' + + 'E\n^\nNTelefon:Telco2-Handy\nE\n^\nNTelefon:Telco3\nE\n^\n' + + 'NTelekommunikation:Fernsehen\nE\n^\nNFernsehen:TV-Company\nE\n' + + '^\nNFernsehen:GEZ\nE\n^\nNLebensmittel\nE\n^') + self.assertEqual( + result['Bank'], + 'D04.12.2013\nT7,12\nCX\n' + + 'POpening Balance\nL[Bargeld]\n^\nD05.12.2013\nCX\nM05.12/' + + '06.42UHR TT TELTOW\nT-29,00\n' + + 'PGA NR00002168 BLZ10000000 0\nL[S-Giro]\n^\nD05.12.2013' + + '\nCX\nMsome food\nT-56,37\nPFoodshop Zehlendorf\n' + + 'LLebensmittel\n^\nD06.12.2013\nCX\nMreturn of bottles\n' + + 'T1,45\nPFoodshop Zehlendorf\nLLebensmittel\n^\n') + + @with_transaction() + def test_qiftool_convert_transactions(self): + """ convert_transactions_to_create + """ + pool = Pool() + QifTool = pool.get('cashbook_dataexchange.qiftool') + Category = pool.get('cashbook.category') + Party = pool.get('party.party') + Book = pool.get('cashbook.book') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id}): + types = self.prep_type() + books = Book.create([{ + 'name': 'Cash Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'S-Giro', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'Bargeld', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }]) + self.assertEqual(books[0].name, 'Cash Book') + self.assertEqual(books[1].name, 'S-Giro') + self.assertEqual(books[2].name, 'Bargeld') + self.assertEqual(books[0].btype.rec_name, 'CAS - Cash') + self.assertEqual(books[1].btype.rec_name, 'CAS - Cash') + self.assertEqual(books[2].btype.rec_name, 'CAS - Cash') + + parties = Party.create([{ + 'name': 'Opening Balance', + 'addresses': [('create', [{}])], + }, { + 'name': 'GA NR00002168 BLZ10000000 0', + 'addresses': [('create', [{}])], + }, { + 'name': 'Foodshop Zehlendorf', + 'addresses': [('create', [{}])], + }, { + 'name': 'real,- Teltow', + 'addresses': [('create', [{}])], + }]) + + categories = Category.create([{ + 'name': 'Lebensmittel', + 'cattype': 'out', + 'company': company.id, + }, { + 'name': 'Haushaltschemie', + 'cattype': 'out', + 'company': company.id, + }, { + 'name': 'Kosmetik', + 'cattype': 'out', + 'company': company.id, + }]) + + tr_list = QifTool.qif_read_transactions( + 'D04.12.2013\nT7,12\nCX\n' + + 'POpening Balance\nL[Bargeld]\n^\nD05.12.2013\nCX\n' + + 'M05.12/06.42UHR TT TELTOW\nT-29,00\nPGA NR00002168 ' + + 'BLZ10000000 0\n' + + 'L[S-Giro]\n^\nD05.12.2013\nCX\nMsome food\nT-56,37\n' + + 'PFoodshop Zehlendorf\nLLebensmittel\n^\nD22.10.2020\n' + + 'CX\nMLebensmittel\nT-55,84\nPreal,- Teltow\nLLebensmittel\n' + + 'SLebensmittel\nELebensmittel\n$-49,36\nSKosmetik\n' + + 'EKlopapier\n' + + '$-2,99\nSHaushaltschemie\nESagrotan\n$-3,49\n' + + 'S[S-Giro]\nEtransfer out\n$-3,49\n^\n') + + (to_create, msg_txt, fail_cnt) = \ + QifTool.convert_transactions_to_create(books[0], tr_list) + self.assertEqual(msg_txt, []) + self.assertEqual(to_create, [{ + 'date': date(2013, 12, 4), + 'amount': Decimal('7.12'), + 'state': 'edit', + 'bookingtype': 'mvin', + 'booktransf': books[2].id, + 'description': 'Opening Balance', + }, { + 'date': date(2013, 12, 5), + 'amount': Decimal('29.00'), + 'state': 'edit', + 'bookingtype': 'mvout', + 'booktransf': books[1].id, + 'description': + 'GA NR00002168 BLZ10000000 0; 05.12/06.42UHR ' + + 'TT TELTOW', + }, { + 'date': date(2013, 12, 5), + 'amount': Decimal('56.37'), + 'description': 'some food', + 'state': 'check', + 'bookingtype': 'out', + 'party': parties[2].id, + 'category': categories[0].id, + }, { + 'date': date(2020, 10, 22), + 'amount': Decimal('55.84'), + 'description': 'Lebensmittel', + 'state': 'edit', + 'bookingtype': 'spout', + 'category': categories[0].id, + 'party': parties[3].id, + 'splitlines': [ + ('create', [{ + 'splittype': 'cat', + 'amount': Decimal('49.36'), + 'description': 'Lebensmittel', + 'category': categories[0].id, + }, { + 'splittype': 'cat', + 'amount': Decimal('2.99'), + 'description': 'Klopapier', + 'category': categories[2].id, + }, { + 'splittype': 'cat', + 'amount': Decimal('3.49'), + 'description': 'Sagrotan', + 'category': categories[1].id, + }, { + 'splittype': 'tr', + 'amount': Decimal('3.49'), + 'description': 'transfer out', + 'booktransf': books[1].id, + }])], + }]) + Book.write(*[ + [books[0]], + { + 'lines': [('create', to_create)], + }]) + self.assertEqual(len(books[0].lines), 4) + self.assertEqual(books[0].balance, Decimal('-137.58')) + + @with_transaction() + def test_qiftool_read_transactions(self): + """ read transaction data from text + """ + QifTool = Pool().get('cashbook_dataexchange.qiftool') + + result = QifTool.qif_read_transactions( + 'D04.12.2013\nT7,12\nCX\n' + + 'POpening Balance\nL[Bargeld]\n^\nD05.12.2013\nCX\n' + + 'M05.12/06.42UHR TT TELTOW\nT290,00\nPGA ' + + 'NR00002168 BLZ10000000 0\n' + + 'L[S-Giro]\n^\nD05.12.2013\nCX\nMsome food\nT-56,37\n' + + 'PFoodshop Zehlendorf\nLLebensmittel\n^\nD22.10.2020\n' + + 'CX\nMLebensmittel\nT-55,84\nPreal,- Teltow\nLLebensmittel\n' + + 'SLebensmittel\nELebensmittel\n$-49,36\nSKosmetik\nEKlopapier\n' + + '$-2,99\nSHaushaltschemie\nESagrotan\n$-3,49\n^\n') + self.assertEqual(result, [{ + 'split': [], + 'date': date(2013, 12, 4), + 'amount': Decimal('7.12'), + 'state': 'check', + 'party': 'Opening Balance', + 'account': 'Bargeld', + }, { + 'split': [], + 'date': date(2013, 12, 5), + 'state': 'check', + 'description': '05.12/06.42UHR TT TELTOW', + 'amount': Decimal('290.00'), + 'party': 'GA NR00002168 BLZ10000000 0', + 'account': 'S-Giro', + }, { + 'split': [], + 'date': date(2013, 12, 5), + 'state': 'check', + 'description': 'some food', + 'amount': Decimal('-56.37'), + 'party': 'Foodshop Zehlendorf', + 'category': 'Lebensmittel', + }, { + 'split': [{ + 'category': 'Lebensmittel', + 'description': 'Lebensmittel', + 'amount': Decimal('-49.36'), + }, { + 'category': 'Kosmetik', + 'description': 'Klopapier', + 'amount': Decimal('-2.99'), + }, { + 'category': 'Haushaltschemie', + 'description': 'Sagrotan', + 'amount': Decimal('-3.49'), + }], + 'date': date(2020, 10, 22), + 'state': 'check', + 'description': 'Lebensmittel', + 'amount': Decimal('-55.84'), + 'party': 'real,- Teltow', + 'category': 'Lebensmittel', + }]) + + @with_transaction() + def test_qiftool_read_categories(self): + """ read category-data from text + """ + QifTool = Pool().get('cashbook_dataexchange.qiftool') + + result = QifTool.qif_read_categories( + 'NGehalt\nI\n^\nNGehalt:Zulagen\nI\n^' + + 'NTelekommunikation\nE\n^\nNTelekommunikation:' + + 'Online-Dienste\nE\n^') + self.assertEqual(result, { + 'in': { + 'Gehalt': { + 'type': 'in', + 'childs': { + 'Zulagen': { + 'type': 'in', + 'childs': {}, + }, + }, + }, + }, + 'out': { + 'Telekommunikation': { + 'type': 'out', + 'childs': { + 'Online-Dienste': { + 'type': 'out', + 'childs': {}, + }, + }, + }, + }, + }) + +# end CategoryTestCase diff --git a/tests/party.py b/tests/party.py new file mode 100644 index 0000000..4a6f113 --- /dev/null +++ b/tests/party.py @@ -0,0 +1,71 @@ +# -*- 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.tests.test_tryton import with_transaction +from trytond.pool import Pool +from trytond.transaction import Transaction +from .qifdata import qif_types + + +class PartyTestCase(object): + 'Test cashbook party module' + module = 'cashbook_dataexchange' + + @with_transaction() + def test_wiz_import_party(self): + """ create parties by run wizard + """ + pool = Pool() + Party = pool.get('party.party') + ImportWiz = pool.get( + 'cashbook_dataexchange.qif_imp_wiz', type='wizard') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + 'active_model': 'party.party'}): + (sess_id, start_state, end_state) = ImportWiz.create() + w_obj = ImportWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + # run start + result = ImportWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + + r1 = {} + r1['file_'] = qif_types.encode('utf8') + r1['company'] = company.id + w_obj.start.file_ = r1['file_'] + w_obj.start.company = company.id + + result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') + + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + self.assertEqual( + result['view']['defaults']['info'], + """The following 3 parties are now imported:\n +Opening Balance +GA NR00002168 BLZ10000000 0 +Foodshop Zehlendorf""") + + r1 = {'company': company.id} + result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') + self.assertEqual(list(result.keys()), []) + + ImportWiz.delete(sess_id) + + records = Party.search([], order=[('name', 'ASC')]) + self.assertEqual(len(records), 4) + + self.assertEqual(records[0].rec_name, 'Foodshop Zehlendorf') + self.assertEqual( + records[1].rec_name, 'GA NR00002168 BLZ10000000 0') + self.assertEqual(records[2].rec_name, 'm-ds') + self.assertEqual(records[3].rec_name, 'Opening Balance') + +# end PartyTestCase diff --git a/tests/qifdata.py b/tests/qifdata.py index 9f749cc..1fb99ba 100644 --- a/tests/qifdata.py +++ b/tests/qifdata.py @@ -54,7 +54,7 @@ L[Bargeld] D05.12.2013 CX M05.12/06.42UHR TT TELTOW -T290,00 +T-29,00 PGA NR00002168 BLZ10000000 0 L[S-Giro] ^ @@ -65,6 +65,13 @@ T-56,37 PFoodshop Zehlendorf LLebensmittel ^ +D06.12.2013 +CX +Mreturn of bottles +T1,45 +PFoodshop Zehlendorf +LLebensmittel +^ """ qif_category = """!Type:Cat diff --git a/tests/test_category.py b/tests/test_category.py deleted file mode 100644 index 753aab5..0000000 --- a/tests/test_category.py +++ /dev/null @@ -1,226 +0,0 @@ -# -*- 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.tests.test_tryton import ModuleTestCase, with_transaction -from trytond.pool import Pool -from trytond.transaction import Transaction -from trytond.modules.cashbook.tests import CashbookTestCase -from .qifdata import qif_category, qif_types - - -class CategoryTestCase(CashbookTestCase): - 'Test cashbook categoy module' - module = 'CashbookExchangeTestCase' - - @with_transaction() - def test_wiz_import_category(self): - """ create categories by run wizard - """ - pool = Pool() - Category = pool.get('cashbook.category') - ImportWiz = pool.get('cashbook_dataexchange.qif_imp_wiz', type='wizard') - - company = self.prep_company() - with Transaction().set_context({ - 'company': company.id, - 'active_model': 'cashbook.category', - }): - (sess_id, start_state, end_state) = ImportWiz.create() - w_obj = ImportWiz(sess_id) - self.assertEqual(start_state, 'start') - self.assertEqual(end_state, 'end') - - # run start - result = ImportWiz.execute(sess_id, {}, start_state) - self.assertEqual(list(result.keys()), ['view']) - self.assertEqual(result['view']['defaults']['company'], company.id) - - r1 = {} - r1['file_'] = qif_types.encode('utf8') - r1['company'] = company.id - w_obj.start.file_ = r1['file_'] - w_obj.start.company = company.id - - result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') - - self.assertEqual(list(result.keys()), ['view']) - self.assertEqual(result['view']['defaults']['company'], company.id) - self.assertEqual(result['view']['defaults']['info'], -"""The following categories are now imported:\\n -Gehalt (in) -Gehalt/Zulagen (in) - -Telekommunikation (out) -Telekommunikation/Online-Dienste (out) -Telekommunikation/Telefon (out) -Telekommunikation/Telefon/Test1 (out) -Telekommunikation/Fernsehen (out) -Telefon (out) -Telefon/Telco1-Tablett (out) -Telefon/Telco2-Handy (out) -Telefon/Telco3 (out) -Fernsehen (out) -Fernsehen/TV-Company (out) -Fernsehen/GEZ (out) -Lebensmittel (out)""") - - r1 = {'company': company.id} - result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') - self.assertEqual(list(result.keys()), []) - - ImportWiz.delete(sess_id) - - records = Category.search([], order=[('rec_name', 'ASC')]) - self.assertEqual(len(records), 15) - - self.assertEqual(records[0].rec_name, 'Fernsehen') - self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') - self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') - self.assertEqual(records[3].rec_name, 'Gehalt') - self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') - self.assertEqual(records[5].rec_name, 'Lebensmittel') - self.assertEqual(records[6].rec_name, 'Telefon') - self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') - self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') - self.assertEqual(records[9].rec_name, 'Telefon/Telco3') - self.assertEqual(records[10].rec_name, 'Telekommunikation') - self.assertEqual(records[11].rec_name, 'Telekommunikation/Fernsehen') - self.assertEqual(records[12].rec_name, 'Telekommunikation/Online-Dienste') - self.assertEqual(records[13].rec_name, 'Telekommunikation/Telefon') - self.assertEqual(records[14].rec_name, 'Telekommunikation/Telefon/Test1') - - @with_transaction() - def test_category_create_by_qif_emptydb(self): - """ create categories by import a qif-file - """ - pool = Pool() - Category = pool.get('cashbook.category') - - company = self.prep_company() - with Transaction().set_context({ - 'company': company.id, - }): - records = Category.create_from_qif(qif_types) - - records = Category.search([], order=[('rec_name', 'ASC')]) - self.assertEqual(len(records), 15) - - self.assertEqual(records[0].rec_name, 'Fernsehen') - self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') - self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') - self.assertEqual(records[3].rec_name, 'Gehalt') - self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') - self.assertEqual(records[5].rec_name, 'Lebensmittel') - self.assertEqual(records[6].rec_name, 'Telefon') - self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') - self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') - self.assertEqual(records[9].rec_name, 'Telefon/Telco3') - self.assertEqual(records[10].rec_name, 'Telekommunikation') - self.assertEqual(records[11].rec_name, 'Telekommunikation/Fernsehen') - self.assertEqual(records[12].rec_name, 'Telekommunikation/Online-Dienste') - self.assertEqual(records[13].rec_name, 'Telekommunikation/Telefon') - self.assertEqual(records[14].rec_name, 'Telekommunikation/Telefon/Test1') - - @with_transaction() - def test_category_create_by_qif_existing_categories(self): - """ create categories by import a qif-file, - some categories exists already - """ - pool = Pool() - Category = pool.get('cashbook.category') - - company = self.prep_company() - with Transaction().set_context({ - 'company': company.id, - }): - cat1, = Category.create([{ - 'name': 'Telekommunikation', - 'cattype': 'out', - 'childs': [('create', [{ - 'cattype': 'out', - 'name': 'Telefon', - }])], - }]) - - records = Category.search([]) - self.assertEqual(len(records), 2) - self.assertEqual(records[0].rec_name, 'Telekommunikation') - self.assertEqual(records[1].rec_name, 'Telekommunikation/Telefon') - - records1 = Category.create_from_qif(qif_types) - - records = Category.search([], order=[('rec_name', 'ASC')]) - self.assertEqual(len(records), 15) - - self.assertEqual(records[0].rec_name, 'Fernsehen') - self.assertEqual(records[1].rec_name, 'Fernsehen/GEZ') - self.assertEqual(records[2].rec_name, 'Fernsehen/TV-Company') - self.assertEqual(records[3].rec_name, 'Gehalt') - self.assertEqual(records[4].rec_name, 'Gehalt/Zulagen') - self.assertEqual(records[5].rec_name, 'Lebensmittel') - self.assertEqual(records[6].rec_name, 'Telefon') - self.assertEqual(records[7].rec_name, 'Telefon/Telco1-Tablett') - self.assertEqual(records[8].rec_name, 'Telefon/Telco2-Handy') - self.assertEqual(records[9].rec_name, 'Telefon/Telco3') - self.assertEqual(records[10].rec_name, 'Telekommunikation') - self.assertEqual(records[11].rec_name, 'Telekommunikation/Fernsehen') - self.assertEqual(records[12].rec_name, 'Telekommunikation/Online-Dienste') - self.assertEqual(records[13].rec_name, 'Telekommunikation/Telefon') - self.assertEqual(records[14].rec_name, 'Telekommunikation/Telefon/Test1') - - @with_transaction() - def test_qiftool_split_types(self): - """ split file-content by types - """ - QifTool = Pool().get('cashbook_dataexchange.qiftool') - - result = QifTool.split_by_type(qif_types) - self.assertEqual(len(result.keys()), 2) - self.assertEqual(result['Cat'], 'NGehalt\nI\n^\nNGehalt:Zulagen\n'+ - 'I\n^\nNTelekommunikation\nE\n^\nNTelekommunikation:Online-Dienste\n'+ - 'E\n^\nNTelekommunikation:Telefon\nE\n^\nNTelekommunikation:Telefon:Test1\n'+ - 'E\n^\nNTelefon:Telco1-Tablett\n'+ - 'E\n^\nNTelefon:Telco2-Handy\nE\n^\nNTelefon:Telco3\nE\n^\n'+ - 'NTelekommunikation:Fernsehen\nE\n^\nNFernsehen:TV-Company\nE\n'+ - '^\nNFernsehen:GEZ\nE\n^\nNLebensmittel\nE\n^') - self.assertEqual(result['Bank'], 'D04.12.2013\nT7,12\nCX\nPOpening Balance\n'+ - 'L[Bargeld]\n^\nD05.12.2013\nCX\nM05.12/06.42UHR TT TELTOW\nT290,00\n'+ - 'PGA NR00002168 BLZ10000000 0\nL[S-Giro]\n^\nD05.12.2013\nCX\nMsome food\n'+ - 'T-56,37\nPFoodshop Zehlendorf\nLLebensmittel\n^\n') - - @with_transaction() - def test_qiftool_read_categories(self): - """ read category-data from text - """ - QifTool = Pool().get('cashbook_dataexchange.qiftool') - - result = QifTool.qif_read_categories('NGehalt\nI\n^\nNGehalt:Zulagen\nI\n^'+ - 'NTelekommunikation\nE\n^\nNTelekommunikation:Online-Dienste\nE\n^') - self.assertEqual(result, { - 'in': { - 'Gehalt': { - 'type': 'in', - 'childs': { - 'Zulagen': { - 'type': 'in', - 'childs': {}, - }, - }, - }, - }, - 'out': { - 'Telekommunikation': { - 'type': 'out', - 'childs': { - 'Online-Dienste': { - 'type': 'out', - 'childs': {}, - }, - }, - }, - }, - }) - -# end CategoryTestCase diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..a65b349 --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,26 @@ +# -*- 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.tests.test_tryton import ModuleTestCase +from trytond.modules.cashbook.tests.test_module import CashbookTestCase +from .category import CategoryTestCase +from .party import PartyTestCase +from .transaction import TransactionTestCase + + +class CashbookExchangeTestCase( + CashbookTestCase, + CategoryTestCase, + PartyTestCase, + TransactionTestCase, + ModuleTestCase): + 'Test cashbook exchange module' + module = 'cashbook_dataexchange' + +# end CashbookExchangeTestCase + + +del ModuleTestCase diff --git a/tests/transaction.py b/tests/transaction.py new file mode 100644 index 0000000..b4448cf --- /dev/null +++ b/tests/transaction.py @@ -0,0 +1,437 @@ +# -*- 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 datetime import date +from decimal import Decimal +from trytond.tests.test_tryton import with_transaction +from trytond.pool import Pool +from trytond.transaction import Transaction +from .qifdata import qif_types + + +class TransactionTestCase(object): + 'Test cashbook transaction module' + module = 'cashbook_dataexchange' + + @with_transaction() + def test_func_check_counter_transaction(self): + """ check function 'check_counter_transaction' + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + QifTool = pool.get('cashbook_dataexchange.qiftool') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id}): + types = self.prep_type() + books = Book.create([{ + 'name': 'Cash Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'S-Giro', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'date': date(2022, 6, 1), + 'bookingtype': 'mvout', + 'amount': Decimal('10.0'), + 'booktransf': books[1].id, + 'description': 'transfer', + }])], + }]) + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '06/01/2022|to|-10.00 usd|transfer [S-Giro | 0.00 usd | Open]') + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck(books[0].lines) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '06/01/2022|from|10.00 usd|transfer [Cash Book ' + + '| -10.00 usd | Open]') + + self.assertEqual(QifTool.check_counter_transaction(books[1], { + 'booktransf': books[0].id, + 'date': date(2022, 6, 1), + 'amount': Decimal('10.0'), + 'description': 'transfer', + 'bookingtype': 'mvin', + }), True) + + @with_transaction() + def test_wiz_import_transactions(self): + """ create transactions by run wizard + """ + pool = Pool() + Party = pool.get('party.party') + Category = pool.get('cashbook.category') + Book = pool.get('cashbook.book') + ImportWiz = pool.get( + 'cashbook_dataexchange.qif_imp_wiz', type='wizard') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + 'active_model': 'cashbook.book'}): + types = self.prep_type() + books = Book.create([{ + 'name': 'Cash Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'S-Giro', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'Bargeld', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }]) + + Party.create([{ + 'name': 'GA NR00002168 BLZ10000000 0', + 'addresses': [('create', [{}])], + }, { + 'name': 'Foodshop Zehlendorf', + 'addresses': [('create', [{}])], + }, { + 'name': 'Opening Balance', + 'addresses': [('create', [{}])], + }]) + + Category.create([{ + 'name': 'Lebensmittel', + 'cattype': 'out', + }]) + + (sess_id, start_state, end_state) = ImportWiz.create() + w_obj = ImportWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + # run start + result = ImportWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + + r1 = {} + r1['file_'] = qif_types.encode('utf8') + r1['company'] = company.id + r1['book'] = books[0].id + w_obj.start.file_ = r1['file_'] + w_obj.start.company = company.id + w_obj.start.book = books[0].id + + result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') + + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + self.assertEqual( + result['view']['defaults']['info'], + """The following transactionen are now imported: +Credit: usd7.12 +Debit: usd83.92 +Balance: -usd76.80 +Number of transactions: 4""") + + r1 = { + 'company': company.id, + 'book': books[0].id, + } + result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') + self.assertEqual(list(result.keys()), []) + + ImportWiz.delete(sess_id) + + self.assertEqual(len(books[0].lines), 4) + + self.assertEqual( + books[0].lines[0].rec_name, + '12/04/2013|from|7.12 usd|Opening Balance ' + + '[Bargeld | -7.12 usd | Open]') + self.assertEqual( + books[0].lines[1].rec_name, + '12/05/2013|to|-29.00 usd|GA NR00002168 BLZ10000000 ' + + '0; 05.12/06.42 [S-Giro | 29.00 usd | Open]') + self.assertEqual( + books[0].lines[2].rec_name, + '12/05/2013|Exp|-56.37 usd|some food [Lebensmittel]') + self.assertEqual( + books[0].lines[3].rec_name, + '12/06/2013|Exp|1.45 usd|return of bottles [Lebensmittel]') + + self.assertEqual(Book.export_as_qif(books[0]), """!Type:Bank +D12/04/2013 +T7.12 +CX +L[Bargeld] +MOpening Balance +^ +D12/05/2013 +T-29.00 +CX +L[S-Giro] +MGA NR00002168 BLZ10000000 0; 05.12/06.42UHR TT TELTOW +^ +D12/05/2013 +T-56.37 +CX +PFoodshop Zehlendorf +LLebensmittel +Msome food +^ +D12/06/2013 +T1.45 +CX +PFoodshop Zehlendorf +LLebensmittel +Mreturn of bottles +^""") + + @with_transaction() + def test_wiz_import_transactions_transfer(self): + """ create transactions by run wizard, + handle transfers + """ + pool = Pool() + Party = pool.get('party.party') + Category = pool.get('cashbook.category') + Book = pool.get('cashbook.book') + ImportWiz = pool.get( + 'cashbook_dataexchange.qif_imp_wiz', type='wizard') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + 'active_model': 'cashbook.book'}): + types = self.prep_type() + books = Book.create([{ + 'name': 'From Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }, { + 'name': 'To Book', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2010, 1, 1), + }]) + + Party.create([{ + 'name': 'Foodshop Zehlendorf', + 'addresses': [('create', [{}])], + }]) + + Category.create([{ + 'name': 'Lebensmittel', + 'cattype': 'out', + }, { + 'name': 'Fee', + 'cattype': 'out', + }]) + + (sess_id, start_state, end_state) = ImportWiz.create() + w_obj = ImportWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + # run start + result = ImportWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + + r1 = {} + r1['file_'] = """!Type:Bank +D05.12.2013 +CX +Msome food +T-50,25 +PFoodshop Zehlendorf +LLebensmittel +^ +D04.12.2013 +T-7,30 +CX +PTransfer to book +L[To Book] +^""".encode('utf8') + r1['company'] = company.id + r1['book'] = books[0].id + w_obj.start.file_ = r1['file_'] + w_obj.start.company = company.id + w_obj.start.book = books[0].id + + result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') + + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + self.assertEqual( + result['view']['defaults']['info'], + """The following transactionen are now imported: +Credit: usd0.00 +Debit: usd57.55 +Balance: -usd57.55 +Number of transactions: 2""") + + r1 = { + 'company': company.id, + 'book': books[0].id, + } + result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') + self.assertEqual(list(result.keys()), []) + + ImportWiz.delete(sess_id) + + self.assertEqual(len(books[0].lines), 2) + self.assertEqual(len(books[1].lines), 1) + + self.assertEqual( + books[0].lines[0].rec_name, + '12/04/2013|to|-7.30 usd|Transfer to book ' + + '[To Book | 7.30 usd | Open]') + self.assertEqual(books[0].lines[0].state, 'check') + self.assertEqual( + books[0].lines[1].rec_name, + '12/05/2013|Exp|-50.25 usd|some food [Lebensmittel]') + self.assertEqual(books[0].lines[1].state, 'check') + self.assertEqual( + books[1].lines[0].rec_name, + '12/04/2013|from|7.30 usd|Transfer to book ' + + '[From Book | -57.55 usd | Open]') + self.assertEqual(books[1].lines[0].state, 'check') + + # run wizard again - import to 'To Book' + (sess_id, start_state, end_state) = ImportWiz.create() + w_obj = ImportWiz(sess_id) + self.assertEqual(start_state, 'start') + self.assertEqual(end_state, 'end') + + # run start + result = ImportWiz.execute(sess_id, {}, start_state) + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + + r1 = {} + r1['file_'] = """!Type:Bank +D10.12.2013 +CX +Msome food +T-10,00 +PFoodshop Zehlendorf +LLebensmittel +^ +D04.12.2013 +T7,30 +CX +PTransfer to book +L[From Book] +^ +D06.12.2013 +T-10,00 +CX +PFoodshop Zehlendorf +MSplitbooking with category and account +LFee +SFee +EFee for transfer +$-3,00 +S[From Book] +ETransfer to From-Book +$-7,00 +^ +""".encode('utf8') + r1['company'] = company.id + r1['book'] = books[1].id + w_obj.start.file_ = r1['file_'] + w_obj.start.company = company.id + w_obj.start.book = books[1].id + + result = ImportWiz.execute(sess_id, {'start': r1}, 'readf') + + self.assertEqual(list(result.keys()), ['view']) + self.assertEqual(result['view']['defaults']['company'], company.id) + self.assertEqual( + result['view']['defaults']['info'], + """The following transactionen are now imported: +Credit: usd0.00 +Debit: usd20.00 +Balance: -usd20.00 +Number of transactions: 2""") + + r1 = { + 'company': company.id, + 'book': books[1].id, + } + result = ImportWiz.execute(sess_id, {'showinfo': r1}, 'importf') + self.assertEqual(list(result.keys()), []) + + ImportWiz.delete(sess_id) + + self.assertEqual(len(books[0].lines), 3) + self.assertEqual(len(books[1].lines), 3) + + self.assertEqual( + books[0].lines[0].rec_name, + '12/04/2013|to|-7.30 usd|Transfer to book ' + + '[To Book | -12.70 usd | Open]') + self.assertEqual( + books[0].lines[0].state, 'check') + self.assertEqual( + books[0].lines[1].rec_name, + '12/05/2013|Exp|-50.25 usd|some food [Lebensmittel]') + self.assertEqual( + books[0].lines[1].state, 'check') + self.assertEqual( + books[0].lines[2].rec_name, + '12/06/2013|from|7.00 usd|Transfer to From-Book ' + + '[To Book | -12.70 usd | Open]') + self.assertEqual( + books[0].lines[2].state, 'check') + + self.assertEqual( + books[1].lines[0].rec_name, + '12/04/2013|from|7.30 usd|Transfer to book [From Book ' + + '| -50.55 usd | Open]') + self.assertEqual(books[1].lines[0].state, 'check') + self.assertEqual( + books[1].lines[1].rec_name, + '12/06/2013|Exp/Sp|-10.00 usd|Splitbooking with category' + + ' and account [-]') + self.assertEqual(books[1].lines[1].state, 'check') + self.assertEqual( + books[1].lines[2].rec_name, + '12/10/2013|Exp|-10.00 usd|some food [Lebensmittel]') + self.assertEqual(books[1].lines[2].state, 'check') + +# end PartyTestCase diff --git a/tryton.cfg b/tryton.cfg index df0875c..9c2b32e 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,7 +1,8 @@ [tryton] -version=6.0.1 +version=6.0.5 depends: cashbook xml: message.xml qif_import_wiz.xml + qif_export.xml diff --git a/versiondep.txt b/versiondep.txt index 1b31d78..834317e 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -1 +1 @@ -cashbook;6.0.1;6.0.999;mds +cashbook;6.0.28;6.0.999;mds diff --git a/view/wiz_qifimport_info_form.xml b/view/wiz_qifimport_info_form.xml index caff918..9b38048 100644 --- a/view/wiz_qifimport_info_form.xml +++ b/view/wiz_qifimport_info_form.xml @@ -5,9 +5,12 @@ full copyright notices and license terms. -->