From 5ef3a52fdcbfa1d2370e91fd294a0f070e129216 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 29 Dec 2023 14:51:39 +0100 Subject: [PATCH] optimize searcher/sort, add search/sort to balance_ref --- book.py | 88 ++++++++++++++++----- locale/de.po | 6 +- locale/en.po | 4 + message.xml | 3 + tests/book.py | 4 + tests/valuestore.py | 188 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 272 insertions(+), 21 deletions(-) diff --git a/book.py b/book.py index 206e839..56a4d35 100644 --- a/book.py +++ b/book.py @@ -139,7 +139,7 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView): states={ 'invisible': ~Bool(Eval('company_currency')), }, depends=['company_currency_digits', 'company_currency']), - 'get_balance_cashbook') + 'get_balance_cashbook', searcher='search_balance') company_currency = fields.Function(fields.Many2One( readonly=True, string='Company Currency', states={'invisible': True}, @@ -346,44 +346,92 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView): ) return (query, tab_line) + @classmethod + def work_order_balance(cls, tables, field_name): + """ get order-query + """ + pool = Pool() + Book2 = pool.get('cashbook.book') + ValueStore = pool.get('cashbook.values') + context = Transaction().context + + query_date = context.get('date', None) + table, _ = tables[None] + if query_date is not None: + if field_name == 'balance_ref': + raise UserError(gettext( + 'cashbook.msg_nosearch_with_date', + fname=field_name, model=Book2.__name__)) + + (tab_book, tab2) = Book2.get_balance_of_cashbook_sql() + query = tab_book.select( + getattr(tab_book, field_name), + where=tab_book.cashbook == table.id) + return [query] + else: + tab_val = ValueStore.__table__() + tab_book = Book2.__table__() + + query = tab_book.join( + tab_val, + condition=( + tab_book.id == tab_val.cashbook) & ( + tab_val.field_name == field_name), + ).select( + tab_val.numvalue, + where=tab_book.id == table.id) + return [query] + @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] + return Book2.work_order_balance(tables, 'balance') @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] + return Book2.work_order_balance(tables, 'balance_all') - query = tab_book.select( - tab_book.balance_all, - where=tab_book.cashbook == table.id) - return [query] + @staticmethod + def order_balance_ref(tables): + """ order by balance-all + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_balance(tables, 'balance_ref') @classmethod def search_balance(cls, name, clause): """ search in 'balance' """ - (tab_line, tab2) = cls.get_balance_of_cashbook_sql() + ValueStore = Pool().get('cashbook.values') Operator = fields.SQL_OPERATORS[clause[1]] + context = Transaction().context - query = tab_line.select( - tab_line.cashbook, - where=Operator( - getattr(tab_line, name), clause[2])) - return [('id', 'in', query)] + query_date = context.get('date', None) + if query_date is not None: + + if name == 'balance_ref': + raise UserError(gettext( + 'cashbook.msg_nosearch_with_date', + fname=name, model=cls.__name__)) + + (tab_line, tab2) = cls.get_balance_of_cashbook_sql() + query = tab_line.select( + tab_line.cashbook, + where=Operator( + getattr(tab_line, name), clause[2])) + return [('id', 'in', query)] + else: + value_query = ValueStore.search([ + ('field_name', '=', clause[0]), + ('numvalue',) + tuple(clause[1:]), + ], + query=True) + return [('value_store', 'in', value_query)] @classmethod def valuestore_delete_records(cls, records): diff --git a/locale/de.po b/locale/de.po index c441d5b..0e30415 100644 --- a/locale/de.po +++ b/locale/de.po @@ -168,7 +168,11 @@ msgstr "Allgemein" msgctxt "model:ir.message,text:msg_value_exists_in_store" msgid "The value already exists for the record." -msgstr "Der Wert existiert für den Datensatz bereits. " +msgstr "Der Wert existiert für den Datensatz bereits." + +msgctxt "model:ir.message,text:msg_nosearch_with_date" +msgid "Search with 'date' no allowed for field '%(fname)s' on model '%(model)s'." +msgstr "Suche mit 'date' nicht erlaubt für Feld '%(fname)s' auf Modell '%(model)s'." ############# diff --git a/locale/en.po b/locale/en.po index 0a45ad4..7b2424f 100644 --- a/locale/en.po +++ b/locale/en.po @@ -162,6 +162,10 @@ msgctxt "model:ir.message,text:msg_value_exists_in_store" msgid "The value already exists for the record." msgstr "The value already exists for the record." +msgctxt "model:ir.message,text:msg_nosearch_with_date" +msgid "Search with 'date' no allowed for field '%(fname)s' on model '%(model)s'." +msgstr "Search with 'date' no allowed for field '%(fname)s' on model '%(model)s'." + msgctxt "model:res.group,name:group_cashbook" msgid "Cashbook" msgstr "Cashbook" diff --git a/message.xml b/message.xml index ff2d735..57a9823 100644 --- a/message.xml +++ b/message.xml @@ -122,6 +122,9 @@ full copyright notices and license terms. --> The value already exists for the record. + + Search with 'date' no allowed for field '%(fname)s' on model '%(model)s'. + diff --git a/tests/book.py b/tests/book.py index 8678173..fa7d028 100644 --- a/tests/book.py +++ b/tests/book.py @@ -307,6 +307,10 @@ class BookTestCase(object): Book.search_count([('balance_all', '<', Decimal('5.0'))]), 0) + self.assertEqual( + Book.search_count([('balance_ref', '<', Decimal('5.0'))]), + 0) + @with_transaction() def test_book_deny_btype_set_none(self): """ create cashbook, add lines, diff --git a/tests/valuestore.py b/tests/valuestore.py index fed00ab..d6c800c 100644 --- a/tests/valuestore.py +++ b/tests/valuestore.py @@ -539,6 +539,194 @@ class ValuestoreTestCase(object): values[8].rec_name, '[Lev 0/Lev 1b | 0.00 € | Open]|balance_ref|0.00|2') + @with_transaction() + def test_valstore_search_sort_books(self): + """ create cashbooks add lines, search/sort + with and w/o 'date' in context + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + + types = self.prep_type() + company = self.prep_company() + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + + with Transaction().set_context({'company': company.id}): + category = self.prep_category(cattype='in') + party = self.prep_party() + books = Book.create([{ + 'name': 'Cashbook 1', + 'btype': types.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': '10 US$', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('10.0'), + 'party': party.id, + }])]}, { + 'name': 'Cashbook 2', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }, { + 'name': 'Cashbook 3', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + self.assertEqual(len(books), 3) + self.assertEqual(books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual(books[1].rec_name, 'Cashbook 2 | 0.00 € | Open') + self.assertEqual(books[2].rec_name, 'Cashbook 3 | 0.00 € | Open') + + Line.create([{ + 'cashbook': books[1].id, + 'bookingtype': 'in', + 'amount': Decimal('5.0'), + 'date': date(2022, 5, 6), + 'description': '5€ in', + 'category': category.id, + }, { + 'cashbook': books[2].id, + 'bookingtype': 'in', + 'amount': Decimal('6.0'), + 'date': date(2022, 5, 7), + 'description': '6€ in', + 'category': category.id, + }]) + + # no 'date' in context, using stored values + # workers not yet processed + books = Book.search([], order=[('name', 'ASC')]) + self.assertEqual(len(books), 3) + self.assertEqual(books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual(books[1].rec_name, 'Cashbook 2 | 0.00 € | Open') + self.assertEqual(books[2].rec_name, 'Cashbook 3 | 0.00 € | Open') + + self.assertEqual( + Book.search_count([('balance', '=', Decimal('0.0'))]), + 2) + self.assertEqual( + Book.search_count([('balance_all', '=', Decimal('0.0'))]), + 2) + self.assertEqual( + Book.search_count([('balance_ref', '=', Decimal('0.0'))]), + 2) + + # check sorting - using stored values + books = Book.search([], order=[ + ('balance_all', 'DESC'), ('name', 'ASC')]) + self.assertEqual(len(books), 3) + self.assertEqual(books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual(books[1].rec_name, 'Cashbook 2 | 0.00 € | Open') + self.assertEqual(books[2].rec_name, 'Cashbook 3 | 0.00 € | Open') + + # search again with 'date' - using computed values + with Transaction().set_context({'date': date(2022, 5, 6)}): + self.assertEqual( + Book.search_count([('balance', '=', Decimal('5.0'))]), + 1) + self.assertEqual( + Book.search_count([ + ('balance', '>=', Decimal('5.0')), + ('balance', '<', Decimal('9.0')), + ]), + 1) + self.assertEqual( + Book.search_count([ + ('balance_all', '>=', Decimal('5.0'))]), + 3) + self.assertRaisesRegex( + UserError, + "Search with 'date' no allowed for field " + + "'balance_ref' on model 'cashbook.book'.", + Book.search_count, + [('balance_ref', '=', Decimal('0.0'))]) + + self.assertRaisesRegex( + UserError, + "Search with 'date' no allowed for field " + + "'balance_ref' on model 'cashbook.book'.", + Book.search, + [], order=[('balance_ref', 'ASC')]) + + # check sorting - using computed values + books = Book.search([], order=[ + ('balance_all', 'DESC'), + ('name', 'ASC'), + ('balance', 'ASC')]) + self.assertEqual(len(books), 3) + self.assertEqual( + books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual( + books[1].rec_name, 'Cashbook 3 | 0.00 € | Open') + self.assertEqual(books[1].balance_all, Decimal('6.0')) + self.assertEqual( + books[2].rec_name, 'Cashbook 2 | 5.00 € | Open') + + with Transaction().set_context({'date': date(2022, 5, 7)}): + self.assertEqual( + Book.search_count([('balance', '=', Decimal('5.0'))]), + 1) + self.assertEqual( + Book.search_count([ + ('balance', '>=', Decimal('5.0')), + ('balance', '<', Decimal('9.0')), + ]), + 2) + + # run workers + self.prep_valstore_run_worker() + + # check stored values - no 'date' in context + books = Book.search([], order=[('name', 'ASC')]) + self.assertEqual(len(books), 3) + self.assertEqual(books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual(books[1].rec_name, 'Cashbook 2 | 5.00 € | Open') + self.assertEqual(books[2].rec_name, 'Cashbook 3 | 6.00 € | Open') + + # check sorting - using stored values + # run most sorters + books = Book.search([], order=[ + ('balance_all', 'DESC'), + ('name', 'ASC'), + ('balance', 'ASC'), + ('balance_ref', 'ASC')]) + self.assertEqual(len(books), 3) + self.assertEqual(books[0].rec_name, 'Cashbook 1 | 10.00 usd | Open') + self.assertEqual(books[1].rec_name, 'Cashbook 3 | 6.00 € | Open') + self.assertEqual(books[2].rec_name, 'Cashbook 2 | 5.00 € | Open') + + self.assertEqual( + Book.search_count([('balance', '=', Decimal('0.0'))]), + 0) + self.assertEqual( + Book.search_count([('balance', '=', Decimal('5.0'))]), + 1) + self.assertEqual( + Book.search_count([('balance', '=', Decimal('6.0'))]), + 1) + self.assertEqual( + Book.search_count([ + ('balance', '>=', Decimal('5.0')), + ('balance', '<', Decimal('9.0')), + ]), + 2) + self.assertEqual( + Book.search_count([('balance_ref', '=', Decimal('6.0'))]), + 1) + @with_transaction() def test_valstore_maintain_values(self): """ create cashbook, check maintenance -