kategorie: erstellt K. aufs qif-datei, wizard begonnen

This commit is contained in:
Frederik Jaeckel 2022-08-28 22:16:34 +02:00
parent 73985fd095
commit 6a6d9bc5d7
13 changed files with 856 additions and 1 deletions

View file

@ -3,3 +3,4 @@ locale/convert_de2en.py
build/*
mds_cashbook_dataexchange.egg-info/*
dist/*
__pycache__/*

View file

@ -5,8 +5,15 @@
from trytond.pool import Pool
from .category import Category
from .qiftool import QifTool
from .qif_import_wiz import ImportQifWizard, ImportQifWizardStart
def register():
Pool.register(
QifTool,
Category,
ImportQifWizardStart,
module='cashbook_dataexchange', type_='model')
Pool.register(
ImportQifWizard,
module='cashbook_dataexchange', type_='wizard')

View file

@ -7,8 +7,57 @@ from trytond.transaction import Transaction
from trytond.pool import Pool, PoolMeta
class Category(PoolMeta):
class Category(metaclass=PoolMeta):
__name__ = 'cashbook.category'
@classmethod
def create_from_qif(cls, qifdata):
""" add categories from QIF-File-content
"""
pool = Pool()
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():
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))
return Category2.create(to_create)
# end Category

View file

@ -2,3 +2,46 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
#############
# ir.action #
#############
msgctxt "model:ir.action,name:act_import_qif_wizard"
msgid "Import QIF-File"
msgstr "QIF-Datei importieren"
#####################################
# cashbook_dataexchange.qif_imp_wiz #
#####################################
msgctxt "model:cashbook_dataexchange.qif_imp_wiz,name:"
msgid "Import QIF-File"
msgstr "QIF-Datei importieren"
msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,start,end:"
msgid "Cancel"
msgstr "Abbruch"
msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,start,readf:"
msgid "Read File"
msgstr "Datei lesen"
###########################################
# cashbook_dataexchange.qif_imp_wiz.start #
###########################################
msgctxt "model:cashbook_dataexchange.qif_imp_wiz.start,name:"
msgid "Import QIF-File"
msgstr "QIF-Datei importieren"
msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,company:"
msgid "Company"
msgstr "Unternehmen"
msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,file_:"
msgid "QIF-File"
msgstr "QIF-Datei"
msgctxt "help:cashbook_dataexchange.qif_imp_wiz.start,file_:"
msgid "Quicken Interchange Format"
msgstr "Quicken Interchange Format"

32
locale/en.po Normal file
View file

@ -0,0 +1,32 @@
#
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
msgctxt "model:ir.action,name:act_import_qif_wizard"
msgid "Import QIF-File"
msgstr "Import QIF-File"
msgctxt "model:cashbook_dataexchange.qif_imp_wiz,name:"
msgid "Import QIF-File"
msgstr "Import QIF-File"
msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,start,end:"
msgid "Cancel"
msgstr "Cancel"
msgctxt "wizard_button:cashbook_dataexchange.qif_imp_wiz,start,readf:"
msgid "Read File"
msgstr "Read File"
msgctxt "model:cashbook_dataexchange.qif_imp_wiz.start,name:"
msgid "Import QIF-File"
msgstr "Import QIF-File"
msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,company:"
msgid "Company"
msgstr "Company"
msgctxt "field:cashbook_dataexchange.qif_imp_wiz.start,file_:"
msgid "QIF-File"
msgstr "QIF-File"

43
qif_import_wiz.py Normal file
View file

@ -0,0 +1,43 @@
# -*- 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.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
class ImportQifWizardStart(ModelView):
'Import QIF-File'
__name__ = 'cashbook_dataexchange.qif_imp_wiz.start'
company = fields.Many2One(model_name='company.company',
string="Company", required=True,
states={'invisible': True})
file_ = fields.Binary(string="QIF-File", required=True,
help='Quicken Interchange Format')
@classmethod
def default_company(cls):
return Transaction().context.get('company')
# end ImportQifWizardStart
class ImportQifWizard(Wizard):
'Import QIF-File'
__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', \
buttons=[
Button(string='Cancel', state='end', icon='tryton-cancel'),
Button(string='Read File', state='readf', icon='tryton-forward', default=True),
])
# end ImportQifWizard

28
qif_import_wiz.xml Normal file
View file

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<!-- 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. -->
<tryton>
<data>
<record model="ir.ui.view" id="qif_imp_wiz_start_form">
<field name="model">cashbook_dataexchange.qif_imp_wiz.start</field>
<field name="type">form</field>
<field name="name">wiz_qifimport_start_form</field>
</record>
<!-- import qif file wizard -->
<record model="ir.action.wizard" id="act_import_qif_wizard">
<field name="name">Import QIF-File</field>
<field name="wiz_name">cashbook_dataexchange.qif_imp_wiz</field>
</record>
<!-- import categories -->
<record model="ir.action.keyword" id="act_import_qif_wizard-cat-keyword">
<field name="keyword">form_action</field>
<field name="model">cashbook.category,-1</field>
<field name="action" ref="act_import_qif_wizard"/>
</record>
</data>
</tryton>

81
qiftool.py Normal file
View file

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.pool import Pool
from trytond.model import Model
class QifTool(Model):
'QIF Tool'
__name__ = 'cashbook_dataexchange.qiftool'
@classmethod
def split_by_type(cls, qifdata):
""" split file-content by type
"""
lines = qifdata.split('\n')
blocks = {}
current_type = None
for line in lines:
if line.startswith('!Type:'):
current_type = line[len('!Type:'):].strip()
else :
if current_type is None:
continue
if not current_type in blocks.keys():
blocks[current_type] = []
blocks[current_type].append(line.strip())
for block in blocks.keys():
blocks[block] = '\n'.join(blocks[block])
return blocks
@classmethod
def qif_read_categories(cls, catdata):
""" read categories from text
result: {
'in': [{
'<Category-Name>': {
'type': 'in|out',
'childs': [...],
},
},...],
'out': [{},...],
}
"""
def add_category(catdict, namelst, ctype):
""" add category to dict
"""
if not namelst[0] in catdict.keys():
catdict[namelst[0]] = {'type': ctype, 'childs': {}}
if len(namelst) > 1:
catdict[namelst[0]]['childs'] = add_category(
catdict[namelst[0]]['childs'],
namelst[1:],
ctype)
return catdict
categories = {'in': {}, 'out': {}}
for cattxt in catdata.split('^'):
if len(cattxt.strip()) == 0:
continue
catname = None
cattype = None
for line in cattxt.strip().split('\n'):
if line.startswith('N'):
catname = line[1:].strip().split(':')
elif line.startswith('E'):
cattype = 'out'
elif line.startswith('I'):
cattype = 'in'
else :
raise ValueError('invalid line: %s (%s)' % (line, cattxt))
categories[cattype] = add_category(categories[cattype], catname, cattype)
return categories
# end QifTool

23
tests/__init__.py Normal file
View file

@ -0,0 +1,23 @@
# This file is part of 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
__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))
return suite

386
tests/qifdata.py Normal file
View file

@ -0,0 +1,386 @@
# -*- 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.
qif_types = """
!Type:Cat
NGehalt
I
^
NGehalt:Zulagen
I
^
NTelekommunikation
E
^
NTelekommunikation:Online-Dienste
E
^
NTelekommunikation:Telefon
E
^
NTelekommunikation:Telefon:Test1
E
^
NTelefon:Telco1-Tablett
E
^
NTelefon:Telco2-Handy
E
^
NTelefon:Telco3
E
^
NTelekommunikation:Fernsehen
E
^
NFernsehen:TV-Company
E
^
NFernsehen:GEZ
E
^
NLebensmittel
E
^
!Type:Bank
D04.12.2013
T7,12
CX
POpening Balance
L[Bargeld]
^
D05.12.2013
CX
M05.12/06.42UHR TT TELTOW
T290,00
PGA NR00002168 BLZ10000000 0
L[S-Giro]
^
D05.12.2013
CX
Msome food
T-56,37
PFoodshop Zehlendorf
LLebensmittel
^
"""
qif_category = """!Type:Cat
NGehalt
I
^
NGehalt:Zulagen
I
^
NGehalt:Gehalt
I
^
NSonstiges
I
^
NSonstiges:Prämie
I
^
NSonstiges:Auslagen
I
^
NSonstiges:Gebühren
I
^
NZinsen
I
^
NZinsen:Girokonto
I
^
NZinsen:Sparkonto
I
^
NZinsen:Sonstige
I
^
NGeschenke
I
^
NDividende
I
^
NVerkauf
I
^
NSteuern
I
^
NSteuern:Kapitalertragssteuer
I
^
NTelekommunikation
E
^
NTelekommunikation:Online-Dienste
E
^
NTelekommunikation:Telefon
E
^
NTelefon:Telco1-Tablett
E
^
NTelefon:Telco2-Handy
E
^
NTelefon:Telco3
E
^
NTelekommunikation:Fernsehen
E
^
NFernsehen:TV-Company
E
^
NFernsehen:GEZ
E
^
NLebensmittel
E
^
NVersicherungen
E
^
NVersicherungen:Krankenversicherung
E
^
NVersicherungen:Haftpflicht
E
^
NVersicherungen:Haushalt
E
^
NVersicherungen:KFZ
E
^
NHobbies
E
^
NHobbies:Werkzeug
E
^
NHobbies:Sport
E
^
NHobbies:Fahrrad
E
^
NHobbies:Foto
E
^
NComputer
E
^
NComputer:Software
E
^
NComputer:Hardware
E
^
NGeschenke
E
^
NFahrtkosten
E
^
NFahrtkosten:Fahrkarten
E
^
NFahrtkosten:Fahrrad
E
^
NFahrtkosten:Parken
E
^
NFahrtkosten:Tanken
E
^
NFahrtkosten:Maut
E
^
NFahrtkosten:Verwarngeld
E
^
NFahrtkosten:Auto
E
^
NWohnen
E
^
NWohnen:Miete
E
^
NWohnen:Nebenkosten
E
^
NNebenkosten:Strom
E
^
NNebenkosten:Abfall
E
^
NNebenkosten:Gas
E
^
NNebenkosten:Wasser
E
^
NWohnen:Garten
E
^
NWohnen:Garage
E
^
NSteuern
E
^
NSteuern:Sozialabgaben
E
^
NSteuern:Solidarzuschlag
E
^
NSteuern:Pflegeversicherung
E
^
NSteuern:Einkommenssteuer
E
^
NSteuern:Rentenversicherung
E
^
NSteuern:Sonstige
E
^
NSteuern:KFZ-Steuer
E
^
NMedikamente
E
^
NKleidung
E
^
NSonstiges
E
^
NSonstiges:Bankgebühren
E
^
NSonstiges:Sonstiges
E
^
NSonstiges:Versandkosten
E
^
NSonstiges:Sehhilfe
E
^
NSonstiges:Gebühr
E
^
NSonstiges:Auslage
E
^
NSonstiges:Gutschein
E
^
NSpenden
E
^
NUnterhaltung
E
^
NUnterhaltung:Musik, Kino
E
^
NUnterhaltung:Reisen
E
^
NUnterhaltung:Ausgehen
E
^
NUnterhaltung:Sport
E
^
NUnterhaltung:Urlaub
E
^
NUnterhaltung:Video
E
^
NUnterhaltung:Museum
E
^
NUnterhaltung:Spiele
E
^
NBüroartikel
E
^
NAbonnements
E
^
NZeitungen
E
^
NZeitungen:Newspaper1
E
^
NZeitungen:Newspaper2
E
^
NZeitungen:Newspaper3
E
^
NBücher
E
^
NKosmetik
E
^
NRentenfonds
E
^
NEinrichtung
E
^
NEinrichtung:Technik
E
^
NEinrichtung:Möbel
E
^
NEinrichtung:Haushalt
E
^
NZinsen
E
^
NZinsen:Sollzinsen
E
^
NHaushaltschemie
E
^
NGesundheit
E
^
NGesundheit:Zahnarzt
E
^
NLuxusgüter
E
^
NLuxusgüter:Uhr
E
^
"""

150
tests/test_category.py Normal file
View file

@ -0,0 +1,150 @@
# -*- 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_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=[('name', 'ASC')])
self.assertEqual(len(records), 15)
self.assertEqual(records[0].rec_name, 'Fernsehen')
self.assertEqual(records[1].rec_name, 'Telekommunikation/Fernsehen')
self.assertEqual(records[2].rec_name, 'Gehalt')
self.assertEqual(records[3].rec_name, 'Fernsehen/GEZ')
self.assertEqual(records[4].rec_name, 'Lebensmittel')
self.assertEqual(records[5].rec_name, 'Telekommunikation/Online-Dienste')
self.assertEqual(records[6].rec_name, 'Telefon/Telco1-Tablett')
self.assertEqual(records[7].rec_name, 'Telefon/Telco2-Handy')
self.assertEqual(records[8].rec_name, 'Telefon/Telco3')
self.assertEqual(records[9].rec_name, 'Telekommunikation/Telefon')
self.assertEqual(records[10].rec_name, 'Telefon')
self.assertEqual(records[11].rec_name, 'Telekommunikation')
self.assertEqual(records[12].rec_name, 'Telekommunikation/Telefon/Test1')
self.assertEqual(records[13].rec_name, 'Fernsehen/TV-Company')
self.assertEqual(records[14].rec_name, 'Gehalt/Zulagen')
@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/Telefon')
self.assertEqual(records[1].rec_name, 'Telekommunikation')
records1 = Category.create_from_qif(qif_types)
records = Category.search([], order=[('name', 'ASC')])
self.assertEqual(len(records), 15)
for rec in records:
print('-rec:', rec.rec_name)
self.assertEqual(records[0].rec_name, 'Telekommunikation/Fernsehen')
self.assertEqual(records[1].rec_name, 'Fernsehen')
self.assertEqual(records[2].rec_name, 'Gehalt')
self.assertEqual(records[3].rec_name, 'Fernsehen/GEZ')
self.assertEqual(records[4].rec_name, 'Lebensmittel')
self.assertEqual(records[5].rec_name, 'Telekommunikation/Online-Dienste')
self.assertEqual(records[6].rec_name, 'Telefon/Telco1-Tablett')
self.assertEqual(records[7].rec_name, 'Telefon/Telco2-Handy')
self.assertEqual(records[8].rec_name, 'Telefon/Telco3')
self.assertEqual(records[9].rec_name, 'Telefon')
self.assertEqual(records[10].rec_name, 'Telekommunikation/Telefon')
self.assertEqual(records[11].rec_name, 'Telekommunikation')
self.assertEqual(records[12].rec_name, 'Telekommunikation/Telefon/Test1')
self.assertEqual(records[13].rec_name, 'Fernsehen/TV-Company')
self.assertEqual(records[14].rec_name, 'Gehalt/Zulagen')
@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

View file

@ -3,3 +3,4 @@ version=6.0.1
depends:
cashbook
xml:
qif_import_wiz.xml

View file

@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- 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. -->
<form col="2">
<label name="company"/>
<field name="company"/>
<label name="file_"/>
<field name="file_"/>
</form>