kategorie: hierarchische sortierung, sequence-spalte entfernt
This commit is contained in:
parent
7fd42c0b42
commit
64e9bab592
6 changed files with 88 additions and 245 deletions
85
category.py
85
category.py
|
@ -3,14 +3,46 @@
|
|||
# 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, Exclude, tree, sequence_ordered
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique, Exclude, tree
|
||||
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
|
||||
from sql import With
|
||||
from sql.functions import Function
|
||||
from sql import With, Literal
|
||||
|
||||
|
||||
class ArrayApppend(Function):
|
||||
""" sql: array_append
|
||||
"""
|
||||
__slots__ = ()
|
||||
_function = 'ARRAY_APPEND'
|
||||
|
||||
# end ArrayApppend
|
||||
|
||||
|
||||
class ArrayToString(Function):
|
||||
""" sql: array_to_string
|
||||
"""
|
||||
__slots__ = ()
|
||||
_function = 'ARRAY_TO_STRING'
|
||||
|
||||
# end ArrayToString
|
||||
|
||||
|
||||
class Array(Function):
|
||||
""" sql: array-type
|
||||
"""
|
||||
__slots__ = ()
|
||||
_function = 'ARRAY'
|
||||
|
||||
def __str__(self):
|
||||
return self._function + '[' + ', '.join(
|
||||
map(self._format, self.args)) + ']'
|
||||
|
||||
# end Array
|
||||
|
||||
|
||||
sel_categorytype = [
|
||||
|
@ -19,7 +51,7 @@ sel_categorytype = [
|
|||
]
|
||||
|
||||
|
||||
class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
|
||||
class Category(tree(separator='/'), ModelSQL, ModelView):
|
||||
'Category'
|
||||
__name__ = 'cashbook.category'
|
||||
|
||||
|
@ -38,7 +70,6 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
|
|||
|
||||
company = fields.Many2One(string='Company', model_name='company.company',
|
||||
required=True, ondelete="RESTRICT")
|
||||
sequence = fields.Integer(string='Sequence', select=True)
|
||||
parent = fields.Many2One(string="Parent",
|
||||
model_name='cashbook.category', ondelete='RESTRICT',
|
||||
left='left', right='right')
|
||||
|
@ -47,10 +78,15 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
|
|||
left = fields.Integer(string='Left', required=True, select=True)
|
||||
right = fields.Integer(string='Right', required=True, select=True)
|
||||
|
||||
@classmethod
|
||||
def __register__(cls, module_name):
|
||||
super(Category, cls).__register__(module_name)
|
||||
cls.migrate_sequence(module_name)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Category, cls).__setup__()
|
||||
cls._order.insert(0, ('name', 'ASC'))
|
||||
cls._order.insert(0, ('rec_name', 'ASC'))
|
||||
t = cls.__table__()
|
||||
cls._sql_constraints.extend([
|
||||
('name_uniq',
|
||||
|
@ -64,6 +100,13 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
|
|||
'cashbook.msg_category_name_unique'),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def migrate_sequence(cls, module_name):
|
||||
""" remove colum 'sequence'
|
||||
"""
|
||||
table = cls.__table_handler__(module_name)
|
||||
table.drop_column('sequence')
|
||||
|
||||
@classmethod
|
||||
def default_cattype(cls):
|
||||
return 'out'
|
||||
|
@ -83,28 +126,30 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
|
|||
@staticmethod
|
||||
def order_rec_name(tables):
|
||||
""" order by pos
|
||||
a recursive sorting
|
||||
"""
|
||||
Category2 = Pool().get('cashbook.category')
|
||||
tab_cat = Category2.__table__()
|
||||
tab_cat2 = Category2.__table__()
|
||||
table, _ = tables[None]
|
||||
|
||||
categories = With('id', 'name', 'name_path', recursive=True)
|
||||
categories.query = select(tab_cat.id, tab_cat.name, array[])
|
||||
categories.query = tab_cat.select(
|
||||
tab_cat.id, tab_cat.name, Array(tab_cat.name),
|
||||
where = tab_cat.parent==None,
|
||||
)
|
||||
categories.query |= tab_cat2.join(categories,
|
||||
condition=categories.id==tab_cat2.parent,
|
||||
).select(
|
||||
tab_cat2.id, tab_cat2.name, ArrayApppend(categories.name_path, tab_cat2.name),
|
||||
)
|
||||
categories.query.all_ = True
|
||||
|
||||
# ~ with recursive categories (id, level, name, name_path) as (
|
||||
# ~ select "a"."id", 0, "a"."name", array["a"."name"]
|
||||
# ~ from cashbook_category as "a"
|
||||
# ~ where "a"."parent" is null
|
||||
|
||||
# ~ union all
|
||||
|
||||
# ~ select "b"."id", "c"."level" + 1, "b"."name", array_append("c"."name_path", "b"."name")
|
||||
# ~ from cashbook_category as "b"
|
||||
# ~ inner join categories as "c" on "c"."id" = "b"."parent"
|
||||
# ~ )
|
||||
# ~ select "d"."id", array_to_string("d"."name_path", '/') as "rec_name"
|
||||
# ~ from categories as "d"
|
||||
# ~ order by "rec_name"
|
||||
query = categories.select(
|
||||
ArrayToString(categories.name_path, '/').as_('rec_name'),
|
||||
where = table.id==categories.id,
|
||||
with_ = [categories])
|
||||
return [query]
|
||||
|
||||
@fields.depends('parent', '_parent_parent.cattype')
|
||||
def on_change_with_parent_cattype(self, name=None):
|
||||
|
|
|
@ -1,219 +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.
|
||||
|
||||
file_name = 'bargeld.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/',
|
||||
user='admin',
|
||||
config_file='/home/trytproj/projekt/py3tr60/etc/trytond.conf')
|
||||
|
||||
# check active modules
|
||||
need_modules = ['cashbook', 'party', 'company']
|
||||
|
||||
IrModule = Model.get('ir.module')
|
||||
mod_lst = IrModule.find([
|
||||
('state', '=', 'activated'),
|
||||
('name', 'in', need_modules),
|
||||
])
|
||||
if not len(mod_lst) == len(need_modules):
|
||||
raise ValueError('einige module sind nicht aktiviert!')
|
||||
|
||||
|
||||
def qif_split_by_type(file_name):
|
||||
""" split file by type
|
||||
"""
|
||||
lines = []
|
||||
with open(file_name, 'r') as fhdl:
|
||||
print('-- read file "%s"' % file_name)
|
||||
lines = fhdl.readlines()
|
||||
|
||||
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())
|
||||
|
||||
for block in blocks.keys():
|
||||
blocks[block] = '\n'.join(blocks[block])
|
||||
print ('-- found type: %s (%d bytes)' % (block, len(blocks[block])))
|
||||
|
||||
return blocks
|
||||
|
||||
# end qif_split_by_type
|
||||
|
||||
|
||||
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 = []
|
||||
|
||||
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': cat_name2,
|
||||
'cattype': categories[cat_name]['type'],
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# end get_category_tree
|
||||
|
||||
|
||||
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')
|
||||
|
||||
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')
|
||||
|
||||
if 'Bank' in qif_type_data.keys():
|
||||
bookings = qif_read_bank(qif_type_data['Bank'])
|
||||
|
||||
print('-- bookings:', bookings)
|
|
@ -74,6 +74,26 @@ class CategoryTestCase(ModuleTestCase):
|
|||
('rec_name', '=', 'Level 1b/Level 1b.2b'),
|
||||
]), 1)
|
||||
|
||||
# ordering #1
|
||||
categories = Category.search([], order=[('rec_name', 'ASC')])
|
||||
self.assertEqual(len(categories), 6)
|
||||
self.assertEqual(categories[0].rec_name, 'Level 1')
|
||||
self.assertEqual(categories[1].rec_name, 'Level 1b')
|
||||
self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2a')
|
||||
self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2b')
|
||||
self.assertEqual(categories[4].rec_name, 'Level 1/Level 2a')
|
||||
self.assertEqual(categories[5].rec_name, 'Level 1/Level 2b')
|
||||
|
||||
# ordering #2
|
||||
categories = Category.search([], order=[('rec_name', 'DESC')])
|
||||
self.assertEqual(len(categories), 6)
|
||||
self.assertEqual(categories[0].rec_name, 'Level 1/Level 2b')
|
||||
self.assertEqual(categories[1].rec_name, 'Level 1/Level 2a')
|
||||
self.assertEqual(categories[2].rec_name, 'Level 1b/Level 1b.2b')
|
||||
self.assertEqual(categories[3].rec_name, 'Level 1b/Level 1b.2a')
|
||||
self.assertEqual(categories[4].rec_name, 'Level 1b')
|
||||
self.assertEqual(categories[5].rec_name, 'Level 1')
|
||||
|
||||
@with_transaction()
|
||||
def test_category_create_check_category_type(self):
|
||||
""" create category, update type of category
|
||||
|
|
|
@ -18,8 +18,7 @@ full copyright notices and license terms. -->
|
|||
<field name="company"/>
|
||||
<label name="parent"/>
|
||||
<field name="parent"/>
|
||||
<label name="sequence"/>
|
||||
<field name="sequence"/>
|
||||
<newline/>
|
||||
<field name="childs" colspan="6"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
<!-- 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. -->
|
||||
<tree sequence="sequence">
|
||||
<tree>
|
||||
<field name="rec_name"/>
|
||||
<field name="sequence" tree_invisible="1"/>
|
||||
</tree>
|
||||
|
|
|
@ -2,9 +2,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. -->
|
||||
<tree sequence="sequence">
|
||||
<tree >
|
||||
<field name="name"/>
|
||||
<field name="sequence" tree_invisible="1"/>
|
||||
<field name="parent" tree_invisible="1"/>
|
||||
<field name="childs" tree_invisible="1"/>
|
||||
</tree>
|
||||
|
|
Loading…
Reference in a new issue