cashbook: abfragen für Felder 'balance*' optimiert + suche + sortierung + test
cashbook-liste: verbergen von Kassenbücher ohne Type
This commit is contained in:
parent
5f20001f72
commit
e6baaa92f0
4 changed files with 233 additions and 101 deletions
216
book.py
216
book.py
|
@ -11,11 +11,10 @@ from trytond.transaction import Transaction
|
|||
from trytond.pool import Pool
|
||||
from trytond.report import Report
|
||||
from decimal import Decimal
|
||||
from sql import Literal
|
||||
from sql.aggregate import Sum
|
||||
from sql.conditionals import Case, Coalesce
|
||||
from sql.functions import CurrentDate
|
||||
from .model import order_name_hierarchical
|
||||
from .model import order_name_hierarchical, sub_ids_hierarchical, AnyInArray
|
||||
|
||||
|
||||
STATES = {
|
||||
|
@ -104,14 +103,16 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
|||
'invisible': STATES2['invisible'],
|
||||
'required': ~STATES2['invisible'],
|
||||
}, depends=DEPENDS2+['lines'])
|
||||
balance = fields.Function(fields.Numeric(string='Balance', readonly=True,
|
||||
balance = fields.Function(fields.Numeric(string='Balance',
|
||||
readonly=True, depends=['currency_digits'],
|
||||
help='Balance of bookings to date',
|
||||
digits=(16, Eval('currency_digits', 2)),
|
||||
depends=['currency_digits']), 'on_change_with_balance')
|
||||
digits=(16, Eval('currency_digits', 2))),
|
||||
'get_balance_cashbook', searcher='search_balance')
|
||||
balance_all = fields.Function(fields.Numeric(string='Total balance',
|
||||
readonly=True, help='Balance of all bookings',
|
||||
digits=(16, Eval('currency_digits', 2)),
|
||||
depends=['currency_digits']), 'on_change_with_balance_all')
|
||||
readonly=True, depends=['currency_digits'],
|
||||
help='Balance of all bookings',
|
||||
digits=(16, Eval('currency_digits', 2))),
|
||||
'get_balance_cashbook', searcher='search_balance')
|
||||
|
||||
balance_ref = fields.Function(fields.Numeric(string='Balance (Ref.)',
|
||||
help='Balance in company currency',
|
||||
|
@ -119,7 +120,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
|||
states={
|
||||
'invisible': ~Bool(Eval('company_currency')),
|
||||
}, depends=['company_currency_digits', 'company_currency']),
|
||||
'on_change_with_balance_ref')
|
||||
'get_balance_cashbook')
|
||||
company_currency = fields.Function(fields.Many2One(readonly=True,
|
||||
string='Company Currency', states={'invisible': True},
|
||||
model_name='currency.currency'),
|
||||
|
@ -269,63 +270,125 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
|||
}
|
||||
return recname
|
||||
|
||||
def get_balance_of_cashbooks(self, date_limit=True):
|
||||
""" compute balance
|
||||
@classmethod
|
||||
def get_balance_of_cashbook_sql(cls):
|
||||
""" sql for balance of a single cashbook
|
||||
"""
|
||||
pool = Pool()
|
||||
Line = pool.get('cashbook.line')
|
||||
Book2 = pool.get('cashbook.book')
|
||||
IrDate = pool.get('ir.date')
|
||||
tab_line = Line.__table__()
|
||||
tab_book = Book2.__table__()
|
||||
context = Transaction().context
|
||||
|
||||
query_date = context.get('date', IrDate.today())
|
||||
query = tab_book.join(tab_line,
|
||||
condition=tab_book.id==tab_line.cashbook,
|
||||
).select(
|
||||
tab_line.cashbook,
|
||||
tab_book.currency,
|
||||
Sum(Case(
|
||||
(tab_line.date <= query_date, tab_line.credit - tab_line.debit),
|
||||
else_ = Decimal('0.0'),
|
||||
)).as_('balance'),
|
||||
Sum(tab_line.credit - tab_line.debit).as_('balance_all'),
|
||||
group_by=[tab_line.cashbook, tab_book.currency],
|
||||
)
|
||||
return (query, tab_line)
|
||||
|
||||
@staticmethod
|
||||
def order_balance(tables):
|
||||
""" order by balance
|
||||
"""
|
||||
Book2 = Pool().get('cashbook.book')
|
||||
(tab_book, tab2) = Book2.get_balance_of_cashbook_sql()
|
||||
table, _ = tables[None]
|
||||
|
||||
query = tab_book.select(tab_book.balance,
|
||||
where=tab_book.cashbook==table.id,
|
||||
)
|
||||
return [query]
|
||||
|
||||
@staticmethod
|
||||
def order_balance_all(tables):
|
||||
""" order by balance-all
|
||||
"""
|
||||
Book2 = Pool().get('cashbook.book')
|
||||
(tab_book, tab2) = Book2.get_balance_of_cashbook_sql()
|
||||
table, _ = tables[None]
|
||||
|
||||
query = tab_book.select(tab_book.balance_all,
|
||||
where=tab_book.cashbook==table.id,
|
||||
)
|
||||
return [query]
|
||||
|
||||
@classmethod
|
||||
def search_balance(cls, name, clause):
|
||||
""" search in 'balance'
|
||||
"""
|
||||
(tab_line, tab2) = cls.get_balance_of_cashbook_sql()
|
||||
Operator = fields.SQL_OPERATORS[clause[1]]
|
||||
|
||||
query = tab_line.select(
|
||||
tab_line.cashbook,
|
||||
where=Operator(
|
||||
getattr(tab_line, name), clause[2]),
|
||||
)
|
||||
return [('id', 'in', query)]
|
||||
|
||||
@classmethod
|
||||
def get_balance_cashbook(cls, cashbooks, names):
|
||||
""" get balance of cashbook
|
||||
"""
|
||||
pool = Pool()
|
||||
Book2 = pool.get('cashbook.book')
|
||||
Book3 = pool.get('cashbook.book')
|
||||
Line = pool.get('cashbook.line')
|
||||
Currency = pool.get('currency.currency')
|
||||
IrDate = pool.get('ir.date')
|
||||
|
||||
tab_line = Line.__table__()
|
||||
tab_book = Book3.__table__()
|
||||
Company = pool.get('company.company')
|
||||
tab_book = Book2.__table__()
|
||||
tab_comp = Company.__table__()
|
||||
(tab_line, tab2) = cls.get_balance_of_cashbook_sql()
|
||||
cursor = Transaction().connection.cursor()
|
||||
context = Transaction().context
|
||||
|
||||
# select cashbook-lines from current cashbook and below
|
||||
query = [
|
||||
('cashbook.id', 'in', Book2.search([
|
||||
('parent', 'child_of', [self.id]),
|
||||
], query=True)),
|
||||
]
|
||||
if date_limit == True:
|
||||
dt1 = context.get('date', None)
|
||||
dt2 = IrDate.today()
|
||||
if not isinstance(dt1, type(dt2)):
|
||||
dt1 = dt2
|
||||
query.append(
|
||||
('date', '<=', dt1)
|
||||
)
|
||||
line_query = Line.search(query, query=True)
|
||||
result = {x:{y.id: Decimal('0.0') for y in cashbooks} for x in names}
|
||||
|
||||
# sum lines by currency
|
||||
bal_by_currency = line_query.join(tab_line,
|
||||
condition=tab_line.id==line_query.id,
|
||||
).join(tab_book,
|
||||
condition=tab_book.id==tab_line.cashbook,
|
||||
# query balances of cashbooks and sub-cashbooks
|
||||
tab_subids = sub_ids_hierarchical('cashbook.book')
|
||||
query = tab_book.join(tab_subids,
|
||||
condition=tab_book.id==tab_subids.parent,
|
||||
).join(tab_comp,
|
||||
condition=tab_book.company==tab_comp.id,
|
||||
).join(tab_line,
|
||||
condition=tab_line.cashbook==AnyInArray(tab_subids.subids),
|
||||
).select(
|
||||
Sum(tab_line.credit - tab_line.debit).as_('balance'),
|
||||
tab_book.currency,
|
||||
group_by=[tab_book.currency],
|
||||
tab_book.id,
|
||||
tab_book.currency.as_('to_currency'),
|
||||
tab_line.currency.as_('from_currency'),
|
||||
tab_comp.currency.as_('company_currency'),
|
||||
Sum(tab_line.balance).as_('balance'),
|
||||
Sum(tab_line.balance_all).as_('balance_all'),
|
||||
group_by=[tab_book.id, tab_line.currency, tab_comp.currency],
|
||||
where=tab_book.id.in_([x.id for x in cashbooks]),
|
||||
)
|
||||
cursor.execute(*query)
|
||||
records = cursor.fetchall()
|
||||
|
||||
if self.id:
|
||||
total = Decimal('0.0')
|
||||
for record in records:
|
||||
values = {
|
||||
'balance': Currency.compute(
|
||||
record[2], record[4], record[1],
|
||||
),
|
||||
'balance_all': Currency.compute(
|
||||
record[2], record[5], record[1],
|
||||
),
|
||||
'balance_ref': Currency.compute(
|
||||
record[2], record[5], record[3],
|
||||
),
|
||||
}
|
||||
|
||||
cursor.execute(*bal_by_currency)
|
||||
balance_lines = cursor.fetchall()
|
||||
|
||||
for line in balance_lines:
|
||||
(balance, id_currency) = line
|
||||
|
||||
total += Currency.compute(
|
||||
Currency(id_currency), # from
|
||||
balance,
|
||||
self.currency, # to
|
||||
)
|
||||
return total
|
||||
for name in names:
|
||||
result[name][record[0]] += values[name]
|
||||
return result
|
||||
|
||||
@fields.depends('btype')
|
||||
def on_change_with_feature(self, name=None):
|
||||
|
@ -354,49 +417,6 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
|
|||
if self.company.currency.id != self.currency.id:
|
||||
return self.company.currency.id
|
||||
|
||||
@fields.depends('id')
|
||||
def on_change_with_balance(self, name=None):
|
||||
""" compute balance until today
|
||||
"""
|
||||
return self.get_balance_of_cashbooks()
|
||||
|
||||
@fields.depends('id')
|
||||
def on_change_with_balance_all(self, name=None):
|
||||
""" compute balance of all bookings
|
||||
"""
|
||||
return self.get_balance_of_cashbooks(date_limit=False)
|
||||
|
||||
@fields.depends('company', 'currency', 'id', 'btype')
|
||||
def on_change_with_balance_ref(self, name=None):
|
||||
""" balance converted to company-currency
|
||||
"""
|
||||
pool = Pool()
|
||||
Line = pool.get('cashbook.line')
|
||||
tab_line = Line.__table__()
|
||||
cursor = Transaction().connection.cursor()
|
||||
|
||||
if self.btype is None:
|
||||
return None
|
||||
|
||||
query = tab_line.select(
|
||||
Sum(tab_line.credit - tab_line.debit),
|
||||
where = tab_line.cashbook == self.id,
|
||||
)
|
||||
|
||||
if self.id:
|
||||
cursor.execute(*query)
|
||||
result = cursor.fetchone()
|
||||
balance = Decimal('0.0')
|
||||
if result:
|
||||
if result[0] is not None:
|
||||
balance += result[0]
|
||||
if self.currency:
|
||||
return self.currency.compute(
|
||||
self.currency, # from
|
||||
balance,
|
||||
self.company.currency # to
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('open')
|
||||
|
|
1
book.xml
1
book.xml
|
@ -30,6 +30,7 @@ full copyright notices and license terms. -->
|
|||
<record model="ir.action.act_window" id="act_book_view">
|
||||
<field name="name">Cashbook</field>
|
||||
<field name="res_model">cashbook.book</field>
|
||||
<field name="domain" eval="[('btype', '!=', None)]" pyson="1"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_book_view-1">
|
||||
<field name="sequence" eval="10"/>
|
||||
|
|
37
model.py
37
model.py
|
@ -10,7 +10,15 @@ from sql import With, Literal
|
|||
from sql.functions import Function
|
||||
|
||||
|
||||
class ArrayApppend(Function):
|
||||
class ArrayAgg(Function):
|
||||
"""input values, including nulls, concatenated into an array.
|
||||
"""
|
||||
__slots__ = ()
|
||||
_function = 'ARRAY_AGG'
|
||||
|
||||
# end ArrayAgg
|
||||
|
||||
class ArrayAppend(Function):
|
||||
""" sql: array_append
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
@ -41,6 +49,31 @@ class Array(Function):
|
|||
# end Array
|
||||
|
||||
|
||||
def sub_ids_hierarchical(model_name):
|
||||
""" get table with id and sub-ids
|
||||
"""
|
||||
Model2 = Pool().get(model_name)
|
||||
tab_mod = Model2.__table__()
|
||||
tab_mod2 = Model2.__table__()
|
||||
|
||||
lines = With('parent', 'id', recursive=True)
|
||||
lines.query = tab_mod.select(
|
||||
tab_mod.id, tab_mod.id,
|
||||
) | tab_mod2.join(lines,
|
||||
condition=lines.id==tab_mod2.parent,
|
||||
).select(
|
||||
lines.parent, tab_mod2.id,
|
||||
)
|
||||
lines.query.all_ = True
|
||||
|
||||
query = lines.select(
|
||||
lines.parent,
|
||||
ArrayAgg(lines.id).as_('subids'),
|
||||
group_by=[lines.parent],
|
||||
with_ = [lines])
|
||||
return query
|
||||
|
||||
|
||||
def order_name_hierarchical(model_name, tables):
|
||||
""" order by pos
|
||||
a recursive sorting
|
||||
|
@ -58,7 +91,7 @@ def order_name_hierarchical(model_name, tables):
|
|||
lines.query |= tab_mod2.join(lines,
|
||||
condition=lines.id==tab_mod2.parent,
|
||||
).select(
|
||||
tab_mod2.id, tab_mod2.name, ArrayApppend(lines.name_path, tab_mod2.name),
|
||||
tab_mod2.id, tab_mod2.name, ArrayAppend(lines.name_path, tab_mod2.name),
|
||||
)
|
||||
lines.query.all_ = True
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ class BookTestCase(ModuleTestCase):
|
|||
self.assertEqual(book.currency.rate, Decimal('1.0'))
|
||||
self.assertEqual(book.company_currency, None)
|
||||
self.assertEqual(book.balance, Decimal('9.52'))
|
||||
self.assertEqual(book.balance_ref, None)
|
||||
self.assertEqual(book.balance_ref, Decimal('9.52'))
|
||||
self.assertEqual(len(book.lines), 0)
|
||||
self.assertEqual(len(book.childs), 1)
|
||||
|
||||
|
@ -225,6 +225,84 @@ class BookTestCase(ModuleTestCase):
|
|||
Book.delete,
|
||||
[book])
|
||||
|
||||
@with_transaction()
|
||||
def test_book_check_search_and_sort(self):
|
||||
""" create cashbook, check search on balance
|
||||
"""
|
||||
pool = Pool()
|
||||
Book = pool.get('cashbook.book')
|
||||
|
||||
types = self.prep_type()
|
||||
category = self.prep_category(cattype='in')
|
||||
company = self.prep_company()
|
||||
party = self.prep_party()
|
||||
books = Book.create([{
|
||||
'name': 'Book 1',
|
||||
'btype': types.id,
|
||||
'company': company.id,
|
||||
'currency': company.currency.id,
|
||||
'number_sequ': self.prep_sequence().id,
|
||||
'start_date': date(2022, 5, 1),
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 1),
|
||||
'description': 'test 1',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('10.0'),
|
||||
'party': party.id,
|
||||
}])],
|
||||
}, {
|
||||
'name': 'Book 2',
|
||||
'btype': types.id,
|
||||
'company': company.id,
|
||||
'currency': company.currency.id,
|
||||
'number_sequ': self.prep_sequence().id,
|
||||
'start_date': date(2022, 5, 1),
|
||||
'lines': [('create', [{
|
||||
'date': date(2022, 5, 1),
|
||||
'description': 'test 2',
|
||||
'category': category.id,
|
||||
'bookingtype': 'in',
|
||||
'amount': Decimal('100.0'),
|
||||
'party': party.id,
|
||||
}])],
|
||||
}])
|
||||
self.assertEqual(len(books), 2)
|
||||
self.assertEqual(books[0].name, 'Book 1')
|
||||
self.assertEqual(books[0].btype.rec_name, 'CAS - Cash')
|
||||
self.assertEqual(books[1].name, 'Book 2')
|
||||
self.assertEqual(books[1].btype.rec_name, 'CAS - Cash')
|
||||
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance', '=', Decimal('10.0'))]),
|
||||
1)
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance', '>', Decimal('5.0'))]),
|
||||
2)
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance', '<', Decimal('5.0'))]),
|
||||
0)
|
||||
|
||||
books = Book.search([], order=[('balance', 'ASC')])
|
||||
self.assertEqual(len(books), 2)
|
||||
self.assertEqual(books[0].balance, Decimal('10.0'))
|
||||
self.assertEqual(books[1].balance, Decimal('100.0'))
|
||||
|
||||
books = Book.search([], order=[('balance', 'DESC')])
|
||||
self.assertEqual(len(books), 2)
|
||||
self.assertEqual(books[0].balance, Decimal('100.0'))
|
||||
self.assertEqual(books[1].balance, Decimal('10.0'))
|
||||
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance_all', '=', Decimal('10.0'))]),
|
||||
1)
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance_all', '>', Decimal('5.0'))]),
|
||||
2)
|
||||
self.assertEqual(
|
||||
Book.search_count([('balance_all', '<', Decimal('5.0'))]),
|
||||
0)
|
||||
|
||||
@with_transaction()
|
||||
def test_book_deny_btype_set_none(self):
|
||||
""" create cashbook, add lines,
|
||||
|
|
Loading…
Reference in a new issue