diff --git a/category.py b/category.py
index 0d23eb8..ffaf75c 100644
--- a/category.py
+++ b/category.py
@@ -3,12 +3,13 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
-from trytond.model import ModelView, ModelSQL, fields, Unique, tree, sequence_ordered
+from trytond.model import ModelView, ModelSQL, fields, Unique, Exclude, tree, sequence_ordered
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval, If, Bool
from trytond.exceptions import UserError
from trytond.i18n import gettext
+from sql.operators import Equal
sel_categorytype = [
@@ -51,7 +52,15 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
cls._order.insert(0, ('name', 'ASC'))
t = cls.__table__()
cls._sql_constraints.extend([
- ('name_uniq', Unique(t, t.name, t.company, t.parent), 'cashbook.msg_category_name_unique'),
+ ('name_uniq',
+ Unique(t, t.name, t.company, t.parent),
+ 'cashbook.msg_category_name_unique'),
+ ('name2_uniq',
+ Exclude(t,
+ (t.name, Equal),
+ (t.cattype, Equal),
+ where=(t.parent == None)),
+ 'cashbook.msg_category_name_unique'),
])
@classmethod
diff --git a/category.xml b/category.xml
index 896fcd8..516e4c7 100644
--- a/category.xml
+++ b/category.xml
@@ -55,12 +55,6 @@ full copyright notices and license terms. -->
-
- All
-
-
-
-
@@ -92,12 +86,6 @@ full copyright notices and license terms. -->
-
- All
-
-
-
-
diff --git a/importer/import_qif.py b/importer/import_qif.py
index 0b569e5..8947899 100644
--- a/importer/import_qif.py
+++ b/importer/import_qif.py
@@ -4,14 +4,19 @@
# full copyright notices and license terms.
file_name = 'bargeld.qif'
-file_category_income = 'category_income.qif'
-file_category_expense = 'category_expense.qif'
company_name = 'm-ds'
+replace_catnames = [
+ ('Musik/Kino', 'Musik, Kino'),
+ ]
+
from quiffen import Qif
from proteus import config, Model
from datetime import date
import json
+from datetime import datetime
+from decimal import Decimal
+
cfg1 = config.set_trytond(
'postgresql://postgres:test1@localhost:5432/tr44/',
@@ -30,53 +35,161 @@ if not len(mod_lst) == len(need_modules):
raise ValueError('einige module sind nicht aktiviert!')
-def add_category(category, parent, company, cattype):
- """ check or add category
+def qif_split_by_type(file_name):
+ """ split file by type
"""
- Category = Model.get('cashbook.category')
+ lines = []
+ with open(file_name, 'r') as fhdl:
+ print('-- read file "%s"' % file_name)
+ lines = fhdl.readlines()
- query = [
- ('name', '=', category.name),
- ('cattype', '=', cattype),
- ]
+ blocks = {}
+ current_type = None
+ for line in lines:
+ if line.startswith('!Type:'):
+ current_type = line[len('!Type:'):].strip()
+ else :
+ if not current_type in blocks.keys():
+ blocks[current_type] = []
+ blocks[current_type].append(line.strip())
- if parent is None:
- query.append(('parent', '=', None))
- else :
- query.append(('parent.id', '=', parent.id))
+ for block in blocks.keys():
+ blocks[block] = '\n'.join(blocks[block])
+ print ('-- found type: %s (%d bytes)' % (block, len(blocks[block])))
- cat1 = Category.find(query)
- if len(cat1) == 1:
- cashbook_category = cat1[0]
- print('- found:', cashbook_category.rec_name)
- elif len(cat1) == 0:
- print('- new-go:', getattr(parent, 'rec_name', '-'), category.name, category.income, category.expense)
- cashbook_category, = Category.create([{
- 'name': category.name,
- 'cattype': cattype,
- 'parent': getattr(parent, 'id', None),
- }], context={'company': company.id})
- cashbook_category = Category(cashbook_category)
- print('- new-ok:', cashbook_category.rec_name)
- else :
- raise ValueError('invalid num of category')
+ return blocks
- for subcat in category.children:
- add_category(subcat, cashbook_category, company, cattype)
-
-# end add_category
+# end qif_split_by_type
-def get_category_tree(categories):
- """ convert categories in dict-tree
+def qif_read_categories(catdata):
+ """ read categories from text
+ """
+ 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 qif_read_categories
+
+
+def qif_read_bank(bankdata):
+ """ read content of bookings
"""
result = []
- for category in categories:
+
+ def get_amount_from_txt(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)
+
+ for booktxt in bankdata.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'] = get_amount_from_txt(line_txt)
+ elif line.startswith('U'): # total
+ booking['amount'] = get_amount_from_txt(line_txt)
+ elif line.startswith('P'): # party
+ booking['party'] = line_txt
+ elif line.startswith('A'): # address
+ booking['address'] = line_txt
+ elif line.startswith('N'): # address
+ booking['checknumber'] = line_txt
+ elif line.startswith('M'): # memo
+ booking['description'] = line_txt
+ elif line.startswith('C'): # state
+ booking['state'] = {
+ 'X': 'check',
+ '*': 'edit',
+ }.get(line_txt, 'edit')
+ elif line.startswith('L'): # category, account
+ if line_txt.startswith('[') and line_txt.endswith(']'):
+ booking['account'] = line_txt[1:-1]
+ else :
+ booking['category'] = line_txt
+ elif line.startswith('S'): # split: category
+ booking['split'].append({
+ 'category': line_txt,
+ })
+ elif line.startswith('E'): # split: memo
+ booking['split'][-1]['description'] = line_txt
+ elif line.startswith('$'): # split: amount
+ booking['split'][-1]['amount'] = get_amount_from_txt(line_txt)
+ elif line.startswith('£'): # split: amount
+ booking['split'][-1]['amount'] = get_amount_from_txt(line_txt)
+ else :
+ raise ValueError('unknown line-code: %s' % (line))
+
+ result.append(booking)
+ return result
+
+# end qif_read_bank
+
+
+def get_category_create(categories, cattype):
+ """ convert categories in create-list
+ """
+ result = []
+ for cat_name in categories.keys():
+ cat_name2 = cat_name
+ for repl in replace_catnames:
+ cat_name2 = cat_name.replace(repl[0], repl[1])
+
+ if cattype != categories[cat_name]['type']:
+ raise ValueError('cattype dont match')
+
cat1 = {
- 'name': category.name,
- 'type': 'in' if category.income == True else 'out' if category.expense == True else 'ups',
+ 'name': cat_name2,
+ 'cattype': categories[cat_name]['type'],
}
- cat1['childs'] = get_category_tree(category.children)
+
+ if len(categories[cat_name]['childs']) > 0:
+ childs = get_category_create(categories[cat_name]['childs'], cattype)
+ if len(childs) > 0:
+ cat1['childs'] = [('create', childs)]
result.append(cat1)
return result
@@ -86,19 +199,21 @@ def get_category_tree(categories):
Company = Model.get('company.company')
company1, = Company.find([('rec_name', '=', company_name)])
+qif_type_data = qif_split_by_type(file_name)
+if 'Cat' in qif_type_data.keys():
+ Category = Model.get('cashbook.category')
-# income-category
-qif = Qif.parse(file_category_income, day_first=True)
-cat_tree = []
-for catname in qif.categories.keys():
- category = qif.categories[catname]
- #cat_tree.extend(get_category_tree([category]))
- add_category(category, None, company1, 'in')
+ categories = qif_read_categories(qif_type_data['Cat'])
+ to_create = get_category_create(categories['in'], 'in')
+ to_create.extend(get_category_create(categories['out'], 'out'))
+ if len(to_create) > 0:
+ try :
+ catlst = Category.create(to_create, context={'company': company1.id})
+ print('-- created %d categories' % len(catlst))
+ except:
+ print('-- categories alredy exist')
-# expense-category
-qif = Qif.parse(file_category_expense, day_first=True)
-cat_tree = []
-for catname in qif.categories.keys():
- category = qif.categories[catname]
- #cat_tree.extend(get_category_tree([category]))
- add_category(category, None, company1, 'out')
+if 'Bank' in qif_type_data.keys():
+ bookings = qif_read_bank(qif_type_data['Bank'])
+
+ print('-- bookings:', bookings)
diff --git a/locale/de.po b/locale/de.po
index 1148f46..37bc9fb 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -326,10 +326,6 @@ msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out"
msgid "Expense"
msgstr "Ausgaben"
-msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_all"
-msgid "All"
-msgstr "Alle"
-
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in"
msgid "Revenue"
msgstr "Einnahmen"
@@ -338,10 +334,6 @@ msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out"
msgid "Expense"
msgstr "Ausgaben"
-msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_all"
-msgid "All"
-msgstr "Alle"
-
###################
# ir.model.button #
diff --git a/locale/en.po b/locale/en.po
index c6f7965..8606e32 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -294,6 +294,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all"
msgid "All"
msgstr "All"
+msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in"
+msgid "Revenue"
+msgstr "Revenue"
+
+msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_out"
+msgid "Expense"
+msgstr "Expense"
+
+msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_in"
+msgid "Revenue"
+msgstr "Revenue"
+
+msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out"
+msgid "Expense"
+msgstr "Expense"
+
msgctxt "model:ir.model.button,string:line_wfedit_button"
msgid "Edit"
msgstr "Edit"
diff --git a/tests/test_category.py b/tests/test_category.py
index bfea57e..f2e0b6e 100644
--- a/tests/test_category.py
+++ b/tests/test_category.py
@@ -90,6 +90,7 @@ class CategoryTestCase(ModuleTestCase):
cat1, = Category.create([{
'name': 'Test 1',
'description': 'Info',
+ 'cattype': 'in',
}])
self.assertEqual(cat1.name, 'Test 1')
self.assertEqual(cat1.rec_name, 'Test 1')
@@ -97,10 +98,11 @@ class CategoryTestCase(ModuleTestCase):
self.assertEqual(cat1.company.rec_name, 'm-ds')
self.assertEqual(cat1.parent, None)
- # duplicate, allowed
+ # duplicate of different type, allowed
cat2, = Category.create([{
'name': 'Test 1',
'description': 'Info',
+ 'cattype': 'out',
}])
self.assertEqual(cat2.name, 'Test 1')
self.assertEqual(cat2.rec_name, 'Test 1')
@@ -108,6 +110,16 @@ class CategoryTestCase(ModuleTestCase):
self.assertEqual(cat2.company.rec_name, 'm-ds')
self.assertEqual(cat2.parent, None)
+ # deny duplicate of same type
+ self.assertRaisesRegex(UserError,
+ 'The category name already exists at this level.',
+ Category.create,
+ [{
+ 'name': 'Test 1',
+ 'description': 'Info',
+ 'cattype': 'in',
+ }])
+
@with_transaction()
def test_category_create_nodupl_diff_level(self):
""" create category
diff --git a/view/category_list.xml b/view/category_list.xml
index f3ca367..3e88e24 100644
--- a/view/category_list.xml
+++ b/view/category_list.xml
@@ -4,6 +4,5 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
-
diff --git a/view/category_tree.xml b/view/category_tree.xml
index ccc59bd..98449ad 100644
--- a/view/category_tree.xml
+++ b/view/category_tree.xml
@@ -4,7 +4,6 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
-