Compare commits

...

9 commits

Author SHA1 Message Date
Frederik Jaeckel
a84e3ed8ba Version 6.0.3 2022-08-31 10:19:19 +02:00
Frederik Jaeckel
4876b06421 book: digits für startbalance/balance ergänzt 2022-08-31 10:17:29 +02:00
Frederik Jaeckel
619a4e9ed6 kategorie: hierarchische sortierung, sequence-spalte entfernt 2022-08-30 11:56:27 +02:00
Frederik Jaeckel
559a5d0656 kategory: sortierung begnnen 2022-08-29 23:34:36 +02:00
Frederik Jaeckel
2fdee39611 kategorie: constraint gegen gleiche Namen auf toplevel,
importer: list/erstellt kategorie, list transaktionen
2022-08-28 12:24:25 +02:00
Frederik Jaeckel
4df6284257 kategorie: domain-views, importer ergänzt 2022-08-27 09:32:17 +02:00
Frederik Jaeckel
0aa9df2f1d importer begonnen 2022-08-26 23:47:51 +02:00
Frederik Jaeckel
dadcfb618f Etikett ver 6.0.2 zum Änderungssatz 1e230c14c823 hinzugefügt 2022-08-25 15:57:30 +02:00
Frederik Jaeckel
603a9d7477 Version 6.0.2 2022-08-25 15:57:19 +02:00
11 changed files with 262 additions and 18 deletions

View file

@ -14,6 +14,18 @@ Requires
Changes
=======
*6.0.3 - 31.08.2022*
- updt: checks, sorting
*6.0.2 - 25.08.2022*
- add: split-booking
*6.0.1 - 23.08.2022*
- works
*6.0.0 - 05.08.2022*
- init

19
book.py
View file

@ -76,14 +76,16 @@ class Book(Workflow, ModelSQL, ModelView):
),
}, depends=DEPENDS+['lines'])
start_balance = fields.Numeric(string='Initial Amount', required=True,
digits=(16, Eval('currency_digits', 2)),
states={
'readonly': Or(
STATES['readonly'],
Bool(Eval('lines')),
),
}, depends=DEPENDS+['lines'])
balance = fields.Function(fields.Numeric(string='Balance', readonly=True),
'on_change_with_balance')
}, depends=DEPENDS+['lines', 'currency_digits'])
balance = fields.Function(fields.Numeric(string='Balance', readonly=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'on_change_with_balance')
currency = fields.Many2One(string='Currency', required=True,
model_name='currency.currency',
states={
@ -92,6 +94,8 @@ class Book(Workflow, ModelSQL, ModelView):
Bool(Eval('lines', [])),
),
}, depends=DEPENDS+['lines'])
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True), 'on_change_with_currency_digits')
state = fields.Selection(string='State', required=True,
readonly=True, selection=sel_state_book)
state_string = state.translated('state')
@ -196,6 +200,15 @@ class Book(Workflow, ModelSQL, ModelView):
'state': self.state_string,
}
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
""" currency of cashbook
"""
if self.currency:
return self.currency.digits
else:
return 2
@fields.depends('id', 'start_balance')
def on_change_with_balance(self, name=None):
""" compute balance

View file

@ -3,12 +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, 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.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 = [
@ -17,7 +51,7 @@ sel_categorytype = [
]
class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
class Category(tree(separator='/'), ModelSQL, ModelView):
'Category'
__name__ = 'cashbook.category'
@ -36,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')
@ -45,15 +78,35 @@ 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', 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
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'
@ -70,6 +123,34 @@ class Category(tree(separator='/'), sequence_ordered(), ModelSQL, ModelView):
def default_right():
return 0
@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 = 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
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):
""" get type of parent category or None

View file

@ -42,6 +42,20 @@ full copyright notices and license terms. -->
<field name="act_window" ref="act_category_list"/>
</record>
<!-- domain view - list -->
<record model="ir.action.act_window.domain" id="act_category_list_domain_in">
<field name="name">Revenue</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('cattype', '=', 'in')]" pyson="1"/>
<field name="act_window" ref="act_category_list"/>
</record>
<record model="ir.action.act_window.domain" id="act_category_list_domain_out">
<field name="name">Expense</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('cattype', '=', 'out')]" pyson="1"/>
<field name="act_window" ref="act_category_list"/>
</record>
<!-- action view - tree -->
<record model="ir.action.act_window" id="act_category_tree">
<field name="name">Category</field>
@ -59,6 +73,21 @@ full copyright notices and license terms. -->
<field name="act_window" ref="act_category_tree"/>
</record>
<!-- domain view - tree -->
<record model="ir.action.act_window.domain" id="act_category_tree_domain_in">
<field name="name">Revenue</field>
<field name="sequence" eval="10"/>
<field name="domain" eval="[('cattype', '=', 'in')]" pyson="1"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<record model="ir.action.act_window.domain" id="act_category_tree_domain_out">
<field name="name">Expense</field>
<field name="sequence" eval="20"/>
<field name="domain" eval="[('cattype', '=', 'out')]" pyson="1"/>
<field name="act_window" ref="act_category_tree"/>
</record>
<!-- permission -->
<!-- anon: deny all -->
<record model="ir.model.access" id="access_category-anon">

View file

@ -318,6 +318,22 @@ msgctxt "model:ir.action.act_window.domain,name:act_line_domain_all"
msgid "All"
msgstr "Alle"
msgctxt "model:ir.action.act_window.domain,name:act_category_tree_domain_in"
msgid "Revenue"
msgstr "Einnahmen"
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_list_domain_in"
msgid "Revenue"
msgstr "Einnahmen"
msgctxt "model:ir.action.act_window.domain,name:act_category_list_domain_out"
msgid "Expense"
msgstr "Ausgaben"
###################
# ir.model.button #
@ -446,6 +462,10 @@ msgctxt "field:cashbook.book,currency:"
msgid "Currency"
msgstr "Währung"
msgctxt "field:cashbook.book,currency_digits:"
msgid "Currency Digits"
msgstr "Nachkommastellen Währung"
msgctxt "field:cashbook.book,start_balance:"
msgid "Initial Amount"
msgstr "Anfangsbetrag"

View file

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

View file

@ -28,6 +28,72 @@ class CategoryTestCase(ModuleTestCase):
}])
return category
@with_transaction()
def test_category_check_rec_name(self):
""" create category, test rec_name, search, order
"""
pool = Pool()
Category = pool.get('cashbook.category')
company = self.prep_company()
Category.create([{
'company': company.id,
'name': 'Level 1',
'cattype': 'in',
'childs': [('create', [{
'company': company.id,
'name': 'Level 2a',
'cattype': 'in',
}, {
'company': company.id,
'name': 'Level 2b',
'cattype': 'in',
}])],
}, {
'company': company.id,
'name': 'Level 1b',
'cattype': 'in',
'childs': [('create', [{
'company': company.id,
'name': 'Level 1b.2a',
'cattype': 'in',
}, {
'company': company.id,
'name': 'Level 1b.2b',
'cattype': 'in',
}])],
}])
self.assertEqual(Category.search_count([
('rec_name', 'ilike', '%1b.2b%'),
]), 1)
self.assertEqual(Category.search_count([
('rec_name', 'ilike', '%1b.2%'),
]), 2)
self.assertEqual(Category.search_count([
('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
@ -90,6 +156,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 +164,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 +176,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

View file

@ -1,5 +1,5 @@
[tryton]
version=6.0.0
version=6.0.3
depends:
res
currency

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,8 +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="cattype"/>
<field name="sequence" tree_invisible="1"/>
</tree>

View file

@ -2,10 +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="cattype"/>
<field name="sequence" tree_invisible="1"/>
<field name="parent" tree_invisible="1"/>
<field name="childs" tree_invisible="1"/>
</tree>