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. -->
diff --git a/view/wiz_qifimport_start_form.xml b/view/wiz_qifimport_start_form.xml
index 7c80dd1..52a4ee6 100644
--- a/view/wiz_qifimport_start_form.xml
+++ b/view/wiz_qifimport_start_form.xml
@@ -5,6 +5,8 @@ full copyright notices and license terms. -->