kategorie: hierarchische sortierung, sequence-spalte entfernt

This commit is contained in:
Frederik Jaeckel 2022-08-30 11:56:27 +02:00
parent 7fd42c0b42
commit 64e9bab592
6 changed files with 88 additions and 245 deletions

View file

@ -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):

View file

@ -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)

View file

@ -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

View file

@ -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>

View file

@ -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>

View file

@ -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>