From 7099289345855b77644a4d845611927c5ae2e830 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Wed, 6 Dec 2023 21:35:12 +0100 Subject: [PATCH 1/9] Version 7.0.11 --- README.rst | 2 +- tryton.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 41604cc..9813f32 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,6 @@ You can monitor trading fees, dividends and sales profits. Changes ======= -*7.0.0 - 06.12.2023* +*7.0.11 - 06.12.2023* - compatibility to Tryton 7.0 diff --git a/tryton.cfg b/tryton.cfg index 51a708f..2240a03 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=7.0.0 +version=7.0.11 depends: cashbook investment From 0bc55b8076dcdc61ec53f622e1e741dc9d3a8fa5 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 23 Dec 2023 10:44:55 +0100 Subject: [PATCH 3/9] remove caching --- asset.py | 21 ++--------- assetsetting.py | 10 ----- book.py | 97 +++---------------------------------------------- 3 files changed, 9 insertions(+), 119 deletions(-) diff --git a/asset.py b/asset.py index f805724..794e885 100644 --- a/asset.py +++ b/asset.py @@ -3,8 +3,7 @@ # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. -from trytond.pool import Pool, PoolMeta -CACHEKEY_ASSETRATE = 'assetrate-%s' +from trytond.pool import PoolMeta class AssetRate(metaclass=PoolMeta): @@ -14,34 +13,22 @@ class AssetRate(metaclass=PoolMeta): def create(cls, vlist): """ update cache-value """ - MemCache = Pool().get('cashbook.memcache') - records = super(AssetRate, cls).create(vlist) - for rate in records: - MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate) + # TODO: update cashbooks using this asset return records @classmethod def write(cls, *args): """ update cache-value """ - MemCache = Pool().get('cashbook.memcache') - super(AssetRate, cls).write(*args) - - actions = iter(args) - for rates, values in zip(actions, actions): - for rate in rates: - MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate) + # TODO: update cashbooks using this asset @classmethod def delete(cls, records): """ set cache to None """ - MemCache = Pool().get('cashbook.memcache') - - for record in records: - MemCache.record_update(CACHEKEY_ASSETRATE % record.asset.id, None) super(AssetRate, cls).delete(records) + # TODO: update cashbooks using this asset # end diff --git a/assetsetting.py b/assetsetting.py index b5981dd..2d9386b 100644 --- a/assetsetting.py +++ b/assetsetting.py @@ -5,7 +5,6 @@ from trytond.model import ModelSingleton, ModelView, ModelSQL, fields -from trytond.pool import Pool class AssetSetting(ModelSingleton, ModelSQL, ModelView): @@ -25,13 +24,4 @@ class AssetSetting(ModelSingleton, ModelSQL, ModelView): model_name='cashbook.book', ondelete='RESTRICT', help='Profit and loss on sale of assets are recorded in the cash book.') - @classmethod - def write(cls, *args): - """ clear cache-values - """ - MemCache = Pool().get('cashbook.memcache') - - MemCache._cashbook_value_cache.clear_all() - super(AssetSetting, cls).write(*args) - # end AssetSetting diff --git a/book.py b/book.py index b7470e4..5e27b4b 100644 --- a/book.py +++ b/book.py @@ -9,33 +9,14 @@ from trytond.pyson import Eval, Or, Len, Bool, If from trytond.modules.cashbook.book import STATES2, DEPENDS2 from trytond.transaction import Transaction from trytond.report import Report -from trytond.config import config from decimal import Decimal from datetime import timedelta from sql import Literal from sql.functions import CurrentDate from sql.aggregate import Sum from sql.conditionals import Case, Coalesce -from trytond.modules.cashbook.model import sub_ids_hierarchical,\ - CACHEKEY_CURRENCY, AnyInArray -from .asset import CACHEKEY_ASSETRATE - - -# enable/disable caching of cachekey for 'currency.rate' -if config.get( - 'cashbook', 'cache_currency', - default='yes').lower() in ['yes', '1', 'true']: - ENA_CURRKEY = True -else: - ENA_CURRKEY = False - -# enable/disable caching of cachekey for 'currency.rate' -if config.get( - 'cashbook', 'cache_asset', - default='yes').lower() in ['yes', '1', 'true']: - ENA_ASSETKEY = True -else: - ENA_ASSETKEY = False +from trytond.modules.cashbook.model import ( + sub_ids_hierarchical, AnyInArray) class Book(SymbolMixin, metaclass=PoolMeta): @@ -291,7 +272,6 @@ class Book(SymbolMixin, metaclass=PoolMeta): """ pool = Pool() IrDate = pool.get('ir.date') - MemCache = pool.get('cashbook.memcache') cursor = Transaction().connection.cursor() context = Transaction().context result = { @@ -309,40 +289,13 @@ class Book(SymbolMixin, metaclass=PoolMeta): ).quantize(Decimal(str(1/10 ** digits))) query_date = context.get('date', IrDate.today()) - cache_keys = { - x.id: MemCache.get_key_by_record( - name='get_yield_data', - record=x, - query=[{ - 'model': 'cashbook.line', - 'query': [('cashbook.parent', 'child_of', [x.id])], - }, { - 'model': 'currency.currency.rate', - 'query': [('currency.id', '=', x.currency.id)], - 'cachekey' if ENA_CURRKEY else 'disabled': - CACHEKEY_CURRENCY % x.currency.id, - }, { - 'model': 'investment.rate', - 'query': [('asset.id', '=', x.asset.id)], - 'cachekey' if ENA_ASSETKEY else 'disabled': - CACHEKEY_ASSETRATE % x.asset.id, - } if x.asset is not None else {}], - addkeys=[query_date.isoformat()]) - for x in cashbooks - } - - # read from cache - (todo_cashbook, result) = MemCache.read_from_cache( - cashbooks, cache_keys, names, result) - if len(todo_cashbook) == 0: - return result # results for 'total' records_total = [] records_12m = [] - if len(todo_cashbook) > 0: + if cashbooks: (tab_book1, query_total) = cls.get_yield_data_sql() - query_total.where &= tab_book1.id.in_([x.id for x in todo_cashbook]) + query_total.where &= tab_book1.id.in_([x.id for x in cashbooks]) cursor.execute(*query_total) records_total = cursor.fetchall() @@ -351,7 +304,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): date_to=query_date, date_from=query_date - timedelta(days=365), ) - query_12m.where &= tab_book2.id.in_([x.id for x in todo_cashbook]) + query_12m.where &= tab_book2.id.in_([x.id for x in cashbooks]) cursor.execute(*query_12m) records_12m = cursor.fetchall() @@ -370,9 +323,6 @@ class Book(SymbolMixin, metaclass=PoolMeta): record[2], record[4]) result['yield_sales_12m'][record[0]] = quantize_val( record[3], record[4]) - - # store to cache - MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook) return {x: result[x] for x in names} @classmethod @@ -457,10 +407,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): CBook = pool.get('cashbook.book') Uom = pool.get('product.uom') Currency = pool.get('currency.currency') - IrDate = pool.get('ir.date') - MemCache = pool.get('cashbook.memcache') cursor = Transaction().connection.cursor() - context = Transaction().context (query, tab_book) = cls.get_asset_quantity_sql() company_currency = CBook.default_currency() @@ -473,37 +420,6 @@ class Book(SymbolMixin, metaclass=PoolMeta): 'digits'] } - cache_keys = { - x.id: MemCache.get_key_by_record( - name='get_asset_quantity', - record=x, - query=[{ - 'model': 'cashbook.line', - 'query': [('cashbook.parent', 'child_of', [x.id])], - }, { - 'model': 'currency.currency.rate', - 'query': [('currency.id', '=', x.currency.id)], - 'cachekey' if ENA_CURRKEY else 'disabled': - CACHEKEY_CURRENCY % x.currency.id, - }, { - 'model': 'investment.rate', - 'query': [('asset.id', '=', x.asset.id)], - 'cachekey' if ENA_ASSETKEY else 'disabled': - CACHEKEY_ASSETRATE % x.asset.id, - } if x.asset is not None else {}], - addkeys=[ - str(company_currency), - str(context.get('qdate', IrDate.today()).toordinal()), - ]) - for x in cashbooks - } - - # read from cache - (todo_cashbook, result) = MemCache.read_from_cache( - cashbooks, cache_keys, names, result) - if len(todo_cashbook) == 0: - return result - def values_from_record(rdata): """ compute values for record """ @@ -644,9 +560,6 @@ class Book(SymbolMixin, metaclass=PoolMeta): Decimal('100.0') * c_val / p_amount - Decimal('100.0') ).quantize(Decimal(str(1/10 ** digits))) result['digits'][id_book] = None - - # store to cache - MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook) return {x: result[x] for x in names} @fields.depends('id') From 246f03541779daf7ac8c1cc3f2eaac454237688a Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 29 Dec 2023 23:07:39 +0100 Subject: [PATCH 4/9] add worker-based precalculation of cashbook-values --- __init__.py | 2 + book.py | 134 ++++++++++++++++++++++++++++++++++++++++----- tests/book.py | 59 ++++++++++++++++++-- tests/yieldtest.py | 12 ++++ valuestore.py | 26 +++++++++ 5 files changed, 216 insertions(+), 17 deletions(-) create mode 100644 valuestore.py diff --git a/__init__.py b/__init__.py index b4c75e7..a0ba3c1 100644 --- a/__init__.py +++ b/__init__.py @@ -11,6 +11,7 @@ from .line import Line from .splitline import SplitLine from .assetsetting import AssetSetting from .asset import AssetRate +from .valuestore import ValueStore def register(): @@ -22,4 +23,5 @@ def register(): SplitLine, Reconciliation, AssetSetting, + ValueStore, module='cashbook_investment', type_='model') diff --git a/book.py b/book.py index 5e27b4b..814d69e 100644 --- a/book.py +++ b/book.py @@ -5,10 +5,12 @@ from trytond.model import fields, SymbolMixin, Index from trytond.pool import PoolMeta, Pool -from trytond.pyson import Eval, Or, Len, Bool, If +from trytond.pyson import Eval, Or, Bool, If from trytond.modules.cashbook.book import STATES2, DEPENDS2 from trytond.transaction import Transaction from trytond.report import Report +from trytond.exceptions import UserError +from trytond.i18n import gettext from decimal import Decimal from datetime import timedelta from sql import Literal @@ -68,12 +70,14 @@ class Book(SymbolMixin, metaclass=PoolMeta): string='Quantity', help='Quantity of assets until to date', readonly=True, digits=(16, Eval('quantity_digits', 4)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['quantity_digits', 'feature']), 'get_asset_quantity') + depends=['quantity_digits', 'feature']), + 'get_asset_quantity', searcher='search_asset_quantity') quantity_all = fields.Function(fields.Numeric( string='Total Quantity', help='Total quantity of all assets', readonly=True, digits=(16, Eval('quantity_digits', 4)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['quantity_digits', 'feature']), 'get_asset_quantity') + depends=['quantity_digits', 'feature']), + 'get_asset_quantity', searcher='search_asset_quantity') current_value = fields.Function(fields.Numeric( string='Value', help='Valuation of the investment based on the current ' + @@ -81,7 +85,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': ~Eval('show_performance', False)}, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + 'get_asset_quantity', searcher='search_asset_quantity') current_value_ref = fields.Function(fields.Numeric( string='Value (Ref.)', help='Valuation of the investment based on the current stock' + @@ -92,7 +96,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): ~Eval('show_performance', False), ~Bool(Eval('company_currency', -1)))}, depends=['currency_digits', 'show_performance', 'company_currency']), - 'get_asset_quantity') + 'get_asset_quantity', searcher='search_asset_quantity') # performance diff_amount = fields.Function(fields.Numeric( @@ -101,14 +105,14 @@ class Book(SymbolMixin, metaclass=PoolMeta): readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': ~Eval('show_performance', False)}, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + 'get_asset_quantity', searcher='search_asset_quantity') diff_percent = fields.Function(fields.Numeric( string='Percent', help='percentage performance since acquisition', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': ~Eval('show_performance', False)}, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + 'get_asset_quantity', searcher='search_asset_quantity') show_performance = fields.Function(fields.Boolean( string='Performance', readonly=True), 'on_change_with_show_performance') current_rate = fields.Function(fields.Numeric( @@ -117,13 +121,15 @@ class Book(SymbolMixin, metaclass=PoolMeta): 'exchange price.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_asset_quantity') + depends=['currency_digits', 'feature']), + 'get_asset_quantity', searcher='search_asset_quantity') purchase_amount = fields.Function(fields.Numeric( string='Purchase Amount', help='Total purchase amount, from shares and fees.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_asset_quantity') + depends=['currency_digits', 'feature']), + 'get_asset_quantity', searcher='search_asset_quantity') # yield yield_dividend_total = fields.Function(fields.Numeric( @@ -268,6 +274,25 @@ class Book(SymbolMixin, metaclass=PoolMeta): @classmethod def get_yield_data(cls, cashbooks, names): + """ get yield data - stored or computed + """ + context = Transaction().context + + result = {x: {y.id: Decimal('0.0') for y in cashbooks} for x in names} + + # return computed values if 'date' is in context + query_date = context.get('date', None) + if query_date is not None: + return cls.get_yield_values(cashbooks, names) + + for cashbook in cashbooks: + for value in cashbook.value_store: + if value.field_name in names: + result[value.field_name][cashbook.id] = value.numvalue + return result + + @classmethod + def get_yield_values(cls, cashbooks, names): """ collect yield data """ pool = Pool() @@ -345,7 +370,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): (tab_line_yield, query_yield) = Line.get_yield_data_sql() context = Transaction().context - query_date = context.get('qdate', CurrentDate()) + query_date = context.get('date', CurrentDate()) query = tab_book.join( tab_line, condition=(tab_book.id == tab_line.cashbook), @@ -397,7 +422,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): return (query, tab_book) @classmethod - def get_asset_quantity(cls, cashbooks, names): + def get_asset_quantity_values(cls, cashbooks, names): """ get quantities field: quantity, quantity_all, current_value, current_value_ref, diff_amount, diff_percent, @@ -464,8 +489,15 @@ class Book(SymbolMixin, metaclass=PoolMeta): if company_currency is not None else rdata[4]), }) - ids_assetbooks = [x.id for x in cashbooks if x.btype is not None] - ids_nonebtypes = [x.id for x in cashbooks if x.btype is None] + ids_assetbooks = [] + ids_nonebtypes = [] + for x in cashbooks: + if x.btype is None: + if x.id not in ids_nonebtypes: + ids_nonebtypes.append(x.id) + else: + if x.id not in ids_assetbooks: + ids_assetbooks.append(x.id) # get values of asset-cashbooks in 'cashbooks' of type=asset if len(ids_assetbooks) > 0: @@ -562,6 +594,82 @@ class Book(SymbolMixin, metaclass=PoolMeta): result['digits'][id_book] = None return {x: result[x] for x in names} + @classmethod + def get_asset_quantity(cls, cashbooks, names): + """ get quantities - stored or computed + """ + context = Transaction().context + + result = {x: {y.id: Decimal('0.0') for y in cashbooks} for x in names} + + # return computed values if 'date' is in context + query_date = context.get('date', None) + if query_date is not None: + return cls.get_asset_quantity_values(cashbooks, names) + + for cashbook in cashbooks: + for value in cashbook.value_store: + if value.field_name in names: + result[value.field_name][cashbook.id] = value.numvalue + return result + + @classmethod + def search_asset_quantity(cls, name, clause): + """ search in stored data + """ + ValueStore = Pool().get('cashbook.values') + context = Transaction().context + + query_date = context.get('date', None) + if query_date is not None: + raise UserError(gettext( + 'cashbook.msg_nosearch_with_date', + fname=name, model=cls.__name__)) + else: + value_query = ValueStore.search([ + ('field_name', '=', clause[0]), + ('numvalue',) + tuple(clause[1:]), + ], + query=True) + return [('value_store', 'in', value_query)] + + @classmethod + def valuestore_fields(cls): + """ field to update + """ + result = super(Book, cls).valuestore_fields() + + # get_asset_quantity_values + result.extend([ + 'quantity', 'quantity_all', 'current_value', 'current_value_ref', + 'diff_amount', 'diff_percent', 'current_rate', 'purchase_amount', + 'yield_fee_total', 'yield_dividend_total', 'yield_sales', + 'yield_fee_12m', 'yield_dividend_12m', 'yield_sales_12m']) + return result + + @classmethod + def valuestore_update_records(cls, records): + """ compute current values of records, + store to global storage + """ + ValStore = Pool().get('cashbook.values') + + super(Book, cls).valuestore_update_records(records) + + if records: + ValStore.update_values( + cls.get_asset_quantity_values( + records, [ + 'quantity', 'quantity_all', 'current_value', + 'current_value_ref', 'diff_amount', 'diff_percent', + 'current_rate', 'purchase_amount'])) + ValStore.update_values( + cls.get_yield_values( + records, [ + 'yield_fee_total', 'yield_dividend_total', + 'yield_sales', 'yield_fee_12m', 'yield_dividend_12m', + 'yield_sales_12m'])) + @fields.depends('id') def on_change_with_show_performance(self, name=None): """ return True if current or subordered cashbooks diff --git a/tests/book.py b/tests/book.py index d3be598..0878bb3 100644 --- a/tests/book.py +++ b/tests/book.py @@ -174,6 +174,24 @@ class CbInvTestCase(object): self.assertEqual(books[0].diff_amount, Decimal('-5.49')) self.assertEqual(books[0].diff_percent, Decimal('-18.74')) + # searcher + self.assertEqual(Book.search_count([ + ('current_value', '=', Decimal('23.8'))]), 1) + self.assertEqual(Book.search_count([ + ('current_value_ref', '=', Decimal('23.8'))]), 1) + self.assertEqual(Book.search_count([ + ('diff_amount', '=', Decimal('-5.49'))]), 1) + self.assertEqual(Book.search_count([ + ('diff_percent', '=', Decimal('-18.74'))]), 1) + self.assertEqual(Book.search_count([ + ('quantity', '=', Decimal('1.0'))]), 2) + self.assertEqual(Book.search_count([ + ('quantity_all', '=', Decimal('1.0'))]), 2) + self.assertEqual(Book.search_count([ + ('current_rate', '=', Decimal('11.9'))]), 1) + self.assertEqual(Book.search_count([ + ('purchase_amount', '=', Decimal('15.0'))]), 2) + self.assertEqual(len(books[0].childs), 4) self.assertEqual( @@ -327,10 +345,11 @@ class CbInvTestCase(object): # wf --> check Line.wfcheck(book.lines) + self.prep_valstore_run_worker() # check quantities at cashbook with Transaction().set_context({ - 'qdate': date(2022, 5, 5)}): + 'date': date(2022, 5, 5)}): book2, = Book.browse([book]) self.assertEqual(book.asset.rate, Decimal('2.8')) # usd self.assertEqual(book2.quantity, Decimal('1.453')) @@ -340,7 +359,7 @@ class CbInvTestCase(object): self.assertEqual(book2.current_value_ref, Decimal('3.87')) with Transaction().set_context({ - 'qdate': date(2022, 5, 12)}): + 'date': date(2022, 5, 12)}): book2, = Book.browse([book]) self.assertEqual(book2.quantity, Decimal('4.753')) self.assertEqual(book2.quantity_all, Decimal('4.753')) @@ -399,6 +418,17 @@ class CbInvTestCase(object): 'Aurum | 1,750.0000 usd/oz | 05/01/2022') (usd, euro) = self.prep_2nd_currency(company) + + usd.rates[0].date = date(2022, 5, 1) + usd.rates[0].save() + self.assertEqual(len(usd.rates), 1) + self.assertEqual(usd.rates[0].date, date(2022, 5, 1)) + + euro.rates[0].date = date(2022, 5, 1) + euro.rates[0].save() + self.assertEqual(len(euro.rates), 1) + self.assertEqual(euro.rates[0].date, date(2022, 5, 1)) + self.assertEqual(company.currency.rec_name, 'Euro') self.assertEqual(asset.symbol, 'usd/oz') @@ -441,7 +471,7 @@ class CbInvTestCase(object): # check quantities at cashbook with Transaction().set_context({ - 'qdate': date(2022, 5, 1)}): + 'date': date(2022, 5, 1)}): book2, = Book.browse([book]) self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd self.assertEqual(book2.quantity, Decimal('20.0')) @@ -508,6 +538,16 @@ class CbInvTestCase(object): 'Aurum | 1,750.0000 usd/oz | 05/01/2022') (usd, euro) = self.prep_2nd_currency(company) + usd.rates[0].date = date(2022, 5, 1) + usd.rates[0].save() + self.assertEqual(len(usd.rates), 1) + self.assertEqual(usd.rates[0].date, date(2022, 5, 1)) + + euro.rates[0].date = date(2022, 5, 1) + euro.rates[0].save() + self.assertEqual(len(euro.rates), 1) + self.assertEqual(euro.rates[0].date, date(2022, 5, 1)) + self.assertEqual(company.currency.rec_name, 'Euro') self.assertEqual(asset.symbol, 'usd/oz') chf, = Currency.create([{ @@ -562,7 +602,7 @@ class CbInvTestCase(object): # check quantities at cashbook with Transaction().set_context({ - 'qdate': date(2022, 5, 1)}): + 'date': date(2022, 5, 1)}): book2, = Book.browse([book]) self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd self.assertEqual(book2.quantity, Decimal('20.0')) @@ -924,6 +964,7 @@ class CbInvTestCase(object): # set line to 'checked', this creates the counterpart Line.wfcheck(list(book.lines)) + self.prep_valstore_run_worker() self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') self.assertEqual(len(book.lines), 1) @@ -1069,6 +1110,7 @@ class CbInvTestCase(object): self.assertEqual(len(book2.lines), 0) Line.wfcheck(list(book1.lines)) + self.prep_valstore_run_worker() self.assertEqual( book1.rec_name, @@ -1167,6 +1209,7 @@ class CbInvTestCase(object): # set line to 'checked', this creates the counterpart Line.wfcheck(list(book.lines)) + self.prep_valstore_run_worker() self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') self.assertEqual(len(book.lines), 1) @@ -1314,6 +1357,7 @@ class CbInvTestCase(object): # set line to 'checked', this creates the counterpart Line.wfcheck(list(book.lines)) + self.prep_valstore_run_worker() self.assertEqual( book.rec_name, @@ -1498,6 +1542,7 @@ class CbInvTestCase(object): # set line to 'checked', this creates the counterpart Line.wfcheck(list(book.lines)) + self.prep_valstore_run_worker() self.assertEqual( book.rec_name, @@ -1855,6 +1900,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2010,6 +2056,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2192,6 +2239,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2531,6 +2579,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2690,6 +2739,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual(books[0].rec_name, 'Book Cash | -11.00 usd | Open') self.assertEqual(books[0].balance_all, Decimal('-11.0')) @@ -2882,6 +2932,7 @@ class CbInvTestCase(object): self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, diff --git a/tests/yieldtest.py b/tests/yieldtest.py index 5bf99f6..1855c81 100644 --- a/tests/yieldtest.py +++ b/tests/yieldtest.py @@ -152,6 +152,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 1) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -257,6 +258,7 @@ class YieldTestCase(object): }]) Line.wfcheck(book_cash.lines) + self.prep_valstore_run_worker() self.assertEqual( book_asset.rec_name, @@ -366,6 +368,7 @@ class YieldTestCase(object): }]) Line.wfcheck(book_asset.lines) + self.prep_valstore_run_worker() self.assertEqual( book_asset.rec_name, @@ -451,6 +454,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 1) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -532,6 +536,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 1) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -631,6 +636,7 @@ class YieldTestCase(object): }]) Line.wfcheck(book_cash.lines) + self.prep_valstore_run_worker() self.assertEqual( book_asset.rec_name, @@ -743,6 +749,7 @@ class YieldTestCase(object): }]) Line.wfcheck(book_asset.lines) + self.prep_valstore_run_worker() self.assertEqual( book_asset.rec_name, @@ -879,6 +886,7 @@ class YieldTestCase(object): lines[0].rec_name, '05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u') Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -1007,6 +1015,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 2) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -1150,6 +1159,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 1) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -1301,6 +1311,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 3) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, @@ -1438,6 +1449,7 @@ class YieldTestCase(object): self.assertEqual(len(lines), 2) Line.wfcheck(lines) + self.prep_valstore_run_worker() self.assertEqual( lines[0].rec_name, diff --git a/valuestore.py b/valuestore.py new file mode 100644 index 0000000..5337e8c --- /dev/null +++ b/valuestore.py @@ -0,0 +1,26 @@ +# -*- 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. + + +from trytond.pool import PoolMeta + + +class ValueStore(metaclass=PoolMeta): + __name__ = 'cashbook.values' + + @classmethod + def _maintenance_fields(cls): + """ add fields to update job + """ + result = super(ValueStore, cls)._maintenance_fields() + result.extend([ + 'quantity', 'current_value', 'current_value_ref', + 'diff_amount', 'diff_percent', 'current_rate', + 'purchase_amount', 'yield_fee_total', 'yield_dividend_total', + 'yield_sales', 'yield_fee_12m', 'yield_dividend_12m', + 'yield_sales_12m']) + return result + +# end ValueStore From 8c2fb8ed7c09aadc0b282f6de608c65f92f66af9 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 29 Dec 2023 23:09:00 +0100 Subject: [PATCH 5/9] cashbook: optimize for speed for checking rows --- book.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/book.py b/book.py index 814d69e..6a2bfb3 100644 --- a/book.py +++ b/book.py @@ -32,8 +32,8 @@ class Book(SymbolMixin, metaclass=PoolMeta): 'invisible': Eval('feature', '') != 'asset', 'readonly': Or( STATES2['readonly'], - Len(Eval('lines')) > 0)}, - depends=DEPENDS2+['feature', 'lines']) + Eval('has_lines', False))}, + depends=DEPENDS2+['feature', 'has_lines']) quantity_digits = fields.Integer( string='Digits', help='Quantity Digits', domain=[ @@ -44,8 +44,8 @@ class Book(SymbolMixin, metaclass=PoolMeta): 'invisible': Eval('feature', '') != 'asset', 'readonly': Or( STATES2['readonly'], - Len(Eval('lines')) > 0)}, - depends=DEPENDS2+['feature', 'lines']) + Eval('has_lines', False))}, + depends=DEPENDS2+['feature', 'has_lines']) asset_uomcat = fields.Function(fields.Many2One( string='UOM Category', readonly=True, model_name='product.uom.category', @@ -59,8 +59,8 @@ class Book(SymbolMixin, metaclass=PoolMeta): 'invisible': Eval('feature', '') != 'asset', 'readonly': Or( STATES2['readonly'], - Len(Eval('lines')) > 0)}, - depends=DEPENDS2+['feature', 'lines', 'asset_uomcat']) + Eval('has_lines', False))}, + depends=DEPENDS2+['feature', 'asset_uomcat', 'has_lines']) symbol = fields.Function(fields.Char( string='Symbol', readonly=True), 'on_change_with_symbol') asset_symbol = fields.Function(fields.Many2One( From 8ec7026259e786c7bb9d926e85b6f80856559b40 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 30 Dec 2023 10:57:17 +0100 Subject: [PATCH 6/9] add update of cashbooks on update of asset-rates --- asset.py | 38 ++++- tests/test_module.py | 2 + tests/valuestore.py | 365 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 401 insertions(+), 4 deletions(-) create mode 100644 tests/valuestore.py diff --git a/asset.py b/asset.py index 794e885..83ba202 100644 --- a/asset.py +++ b/asset.py @@ -3,7 +3,7 @@ # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. -from trytond.pool import PoolMeta +from trytond.pool import PoolMeta, Pool class AssetRate(metaclass=PoolMeta): @@ -13,22 +13,52 @@ class AssetRate(metaclass=PoolMeta): def create(cls, vlist): """ update cache-value """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + ValueStore = pool.get('cashbook.values') + records = super(AssetRate, cls).create(vlist) - # TODO: update cashbooks using this asset + + ValueStore.update_books( + ValueStore.get_book_by_books( + Cashbook.search([ + ('asset', 'in', [ + x.asset.id for x in records])]))) return records @classmethod def write(cls, *args): """ update cache-value """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + ValueStore = pool.get('cashbook.values') + + actions = iter(args) + all_rates = [] + for rates, values in zip(actions, actions): + all_rates.extend(rates) + super(AssetRate, cls).write(*args) - # TODO: update cashbooks using this asset + + ValueStore.update_books( + ValueStore.get_book_by_books( + Cashbook.search([ + ('asset', 'in', [ + x.asset.id for x in all_rates])]))) @classmethod def delete(cls, records): """ set cache to None """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + ValueStore = pool.get('cashbook.values') + + books = ValueStore.get_book_by_books(Cashbook.search([ + ('asset', 'in', [x.asset.id for x in records])])) + super(AssetRate, cls).delete(records) - # TODO: update cashbooks using this asset + ValueStore.update_books(books) # end diff --git a/tests/test_module.py b/tests/test_module.py index 2b73b65..893468e 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -10,9 +10,11 @@ from trytond.modules.investment.tests.test_module import InvestmentTestCase from .yieldtest import YieldTestCase from .book import CbInvTestCase from .reconciliation import ReconTestCase +from .valuestore import ValueStoreTestCase class CashbookInvestmentTestCase( + ValueStoreTestCase, CashbookTestCase, InvestmentTestCase, CbInvTestCase, diff --git a/tests/valuestore.py b/tests/valuestore.py new file mode 100644 index 0000000..b9aa50f --- /dev/null +++ b/tests/valuestore.py @@ -0,0 +1,365 @@ +# -*- 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. + +from trytond.tests.test_tryton import with_transaction +from trytond.pool import Pool +from trytond.transaction import Transaction +from datetime import date +from decimal import Decimal + + +class ValueStoreTestCase(object): + """ test update of cashbooks on update of asset + """ + @with_transaction() + def test_valstore_update_asset_rate(self): + """ update rate of asset, should update cashbook + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + Queue = pool.get('ir.queue') + ValStore = pool.get('cashbook.values') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + party = self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + Asset.write(*[ + [asset], + { + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('2.5'), + }, { + 'date': date(2022, 5, 2), + 'rate': Decimal('2.8'), + }])], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/u') + + self.assertEqual(Queue.search_count([]), 0) + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Text 1', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('2.5'), + 'party': party.id, + 'quantity': Decimal('1.5'), + }])], + }]) + + # run worker + self.assertEqual( + ValStore.search_count([]), + len(Book.valuestore_fields())) + self.prep_valstore_run_worker() + + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.rec_name, 'Book 1 | 2.50 € | Open | 1.500 u') + self.assertEqual(len(book.lines), 1) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|Rev|2.50 €|Text 1 [Cat1]|1.500 u') + + values = ValStore.search([ + ('cashbook', '=', book.id)], order=[('field_name', 'ASC')]) + self.assertEqual(len(values), len(Book.valuestore_fields())) + + self.assertEqual( + values[0].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance|2.50|2') + self.assertEqual( + values[1].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_all|2.50|2') + self.assertEqual( + values[2].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_ref|2.50|2') + self.assertEqual( + values[3].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_rate|2.67|2') + self.assertEqual( + values[4].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value|4.00|2') + self.assertEqual( + values[5].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value_ref|4.00|2') + self.assertEqual( + values[6].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_amount|1.50|2') + self.assertEqual( + values[7].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_percent|60.00|2') + self.assertEqual( + values[8].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|purchase_amount|2.50|2') + self.assertEqual( + values[9].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity|1.500|3') + self.assertEqual( + values[10].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity_all|1.500|3') + self.assertEqual( + values[11].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_12m|0.00|2') + self.assertEqual( + values[12].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_total|0.00|2') + self.assertEqual( + values[13].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_12m|0.00|2') + self.assertEqual( + values[14].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_total|0.00|2') + self.assertEqual( + values[15].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales|2.50|2') + self.assertEqual( + values[16].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales_12m|0.00|2') + + # add rate + Asset.write(*[ + [asset], + { + 'rates': [('create', [{ + 'date': date(2022, 5, 10), + 'rate': Decimal('3.0'), + }])], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 3.0000 usd/u | 05/10/2022') + + # run worker + self.prep_valstore_run_worker() + + values = ValStore.search([ + ('cashbook', '=', book.id)], order=[('field_name', 'ASC')]) + self.assertEqual(len(values), len(Book.valuestore_fields())) + + self.assertEqual( + values[0].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance|2.50|2') + self.assertEqual( + values[1].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_all|2.50|2') + self.assertEqual( + values[2].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_ref|2.50|2') + self.assertEqual( + values[3].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_rate|2.86|2') + self.assertEqual( + values[4].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value|4.29|2') + self.assertEqual( + values[5].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value_ref|4.29|2') + self.assertEqual( + values[6].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_amount|1.79|2') + self.assertEqual( + values[7].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_percent|71.60|2') + self.assertEqual( + values[8].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|purchase_amount|2.50|2') + self.assertEqual( + values[9].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity|1.500|3') + self.assertEqual( + values[10].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity_all|1.500|3') + self.assertEqual( + values[11].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_12m|0.00|2') + self.assertEqual( + values[12].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_total|0.00|2') + self.assertEqual( + values[13].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_12m|0.00|2') + self.assertEqual( + values[14].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_total|0.00|2') + self.assertEqual( + values[15].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales|2.50|2') + self.assertEqual( + values[16].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales_12m|0.00|2') + + # update rate + self.assertEqual(asset.rates[0].rate, Decimal('3.0')) + self.assertEqual(asset.rates[0].date, date(2022, 5, 10)) + Asset.write(*[ + [asset], + { + 'rates': [('write', [asset.rates[0]], { + 'rate': Decimal('3.5'), + })], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 3.5000 usd/u | 05/10/2022') + + # run worker + self.prep_valstore_run_worker() + + values = ValStore.search([ + ('cashbook', '=', book.id)], order=[('field_name', 'ASC')]) + self.assertEqual(len(values), len(Book.valuestore_fields())) + + self.assertEqual( + values[0].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance|2.50|2') + self.assertEqual( + values[1].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_all|2.50|2') + self.assertEqual( + values[2].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_ref|2.50|2') + self.assertEqual( + values[3].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_rate|3.33|2') + self.assertEqual( + values[4].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value|5.00|2') + self.assertEqual( + values[5].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value_ref|5.00|2') + self.assertEqual( + values[6].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_amount|2.50|2') + self.assertEqual( + values[7].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_percent|100.00|2') + self.assertEqual( + values[8].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|purchase_amount|2.50|2') + self.assertEqual( + values[9].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity|1.500|3') + self.assertEqual( + values[10].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity_all|1.500|3') + self.assertEqual( + values[11].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_12m|0.00|2') + self.assertEqual( + values[12].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_total|0.00|2') + self.assertEqual( + values[13].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_12m|0.00|2') + self.assertEqual( + values[14].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_total|0.00|2') + self.assertEqual( + values[15].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales|2.50|2') + self.assertEqual( + values[16].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales_12m|0.00|2') + + # delete rate + self.assertEqual(asset.rates[0].rate, Decimal('3.5')) + self.assertEqual(asset.rates[0].date, date(2022, 5, 10)) + Asset.write(*[ + [asset], + { + 'rates': [('delete', [asset.rates[0].id])], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022') + + # run worker + self.prep_valstore_run_worker() + + values = ValStore.search([ + ('cashbook', '=', book.id)], order=[('field_name', 'ASC')]) + self.assertEqual(len(values), len(Book.valuestore_fields())) + + self.assertEqual( + values[0].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance|2.50|2') + self.assertEqual( + values[1].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_all|2.50|2') + self.assertEqual( + values[2].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|balance_ref|2.50|2') + self.assertEqual( + values[3].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_rate|2.67|2') + self.assertEqual( + values[4].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value|4.00|2') + self.assertEqual( + values[5].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|current_value_ref|4.00|2') + self.assertEqual( + values[6].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_amount|1.50|2') + self.assertEqual( + values[7].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|diff_percent|60.00|2') + self.assertEqual( + values[8].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|purchase_amount|2.50|2') + self.assertEqual( + values[9].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity|1.500|3') + self.assertEqual( + values[10].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|quantity_all|1.500|3') + self.assertEqual( + values[11].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_12m|0.00|2') + self.assertEqual( + values[12].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_dividend_total|0.00|2') + self.assertEqual( + values[13].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_12m|0.00|2') + self.assertEqual( + values[14].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_fee_total|0.00|2') + self.assertEqual( + values[15].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales|2.50|2') + self.assertEqual( + values[16].rec_name, + '[Book 1 | 2.50 € | Open | 1.500 u]|yield_sales_12m|0.00|2') + +# end ValueStoreTestCase From 9f5c7c6b7d8e61fec64a6dea0dae1f49d46218d3 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 30 Dec 2023 11:18:49 +0100 Subject: [PATCH 7/9] add columns to cashbook tree/list-view --- view/book_list.xml | 9 +++++++++ view/book_tree.xml | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/view/book_list.xml b/view/book_list.xml index 2189834..f874535 100644 --- a/view/book_list.xml +++ b/view/book_list.xml @@ -6,9 +6,18 @@ full copyright notices and license terms. --> + + + + + + + + + diff --git a/view/book_tree.xml b/view/book_tree.xml index 2bb9983..ce3eddc 100644 --- a/view/book_tree.xml +++ b/view/book_tree.xml @@ -5,8 +5,19 @@ full copyright notices and license terms. --> + + + + + + + + + + + From aefea56353b882d65485d0c1d45c57fa5a408ba9 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 30 Dec 2023 11:44:33 +0100 Subject: [PATCH 8/9] add sorting --- book.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++--- tests/book.py | 19 ++++++++ 2 files changed, 136 insertions(+), 7 deletions(-) diff --git a/book.py b/book.py index 6a2bfb3..9e03972 100644 --- a/book.py +++ b/book.py @@ -136,41 +136,48 @@ class Book(SymbolMixin, metaclass=PoolMeta): string='Dividend', help='Total dividends received', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_dividend_12m = fields.Function(fields.Numeric( string='Dividend 1y', help='Dividends received in the last twelve months', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_fee_total = fields.Function(fields.Numeric( string='Trade Fee', help='Total trade fees payed', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_fee_12m = fields.Function(fields.Numeric( string='Trade Fee 1y', help='Trade fees payed in the last twelve month', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_sales = fields.Function(fields.Numeric( string='Sales', help='Total profit or loss on sale of shares.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_sales_12m = fields.Function(fields.Numeric( string='Sales 1y', help='Total profit or loss on sale of shares in the last twelve month.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'get_yield_data') + depends=['currency_digits', 'feature']), + 'get_yield_data', searcher='search_asset_quantity') yield_balance = fields.Function(fields.Numeric( string='Total Yield', help='Total income: price gain + dividends + sales gains - fees', readonly=True, digits=(16, Eval('currency_digits', 2)), states={'invisible': Eval('feature', '') != 'asset'}, - depends=['currency_digits', 'feature']), 'on_change_with_yield_balance') + depends=['currency_digits', 'feature']), + 'on_change_with_yield_balance', searcher='search_asset_quantity') @classmethod def __setup__(cls): @@ -208,6 +215,109 @@ class Book(SymbolMixin, metaclass=PoolMeta): } return recname + @classmethod + def work_order_assets(cls, tables, field_name): + """ get order-query + """ + Book2 = Pool().get('cashbook.book') + + if 'date' in Transaction().context: + raise UserError(gettext( + 'cashbook.msg_nosearch_with_date', + fname=field_name, model=Book2.__name__)) + return Book2.work_order_balance(tables, field_name) + + @staticmethod + def order_current_value(tables): + """ order by current_value + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'current_value') + + @staticmethod + def order_purchase_amount(tables): + """ order by purchase_amount + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'purchase_amount') + + @staticmethod + def order_diff_amount(tables): + """ order by diff_amount + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'diff_amount') + + @staticmethod + def order_yield_balance(tables): + """ order by yield_balance + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_balance') + + @staticmethod + def order_diff_percent(tables): + """ order by diff_percent + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'diff_percent') + + @staticmethod + def order_quantity(tables): + """ order by quantity + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'quantity') + + @staticmethod + def order_quantity_all(tables): + """ order by quantity_all + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'quantity_all') + + @staticmethod + def order_yield_sales(tables): + """ order by yield_sales + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_sales') + + @staticmethod + def order_yield_sales_12m(tables): + """ order by yield_sales_12m + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_sales_12m') + + @staticmethod + def order_yield_dividend_total(tables): + """ order by yield_dividend_total + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_dividend_total') + + @staticmethod + def order_yield_dividend_12m(tables): + """ order by yield_dividend_12m + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_dividend_12m') + + @staticmethod + def order_yield_fee_total(tables): + """ order by yield_fee_total + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_fee_total') + + @staticmethod + def order_yield_fee_12m(tables): + """ order by yield_fee_12m + """ + Book2 = Pool().get('cashbook.book') + return Book2.work_order_assets(tables, 'yield_fee_12m') + @fields.depends('asset', 'quantity_uom') def on_change_asset(self): """ get uom from asset diff --git a/tests/book.py b/tests/book.py index 0878bb3..3c89723 100644 --- a/tests/book.py +++ b/tests/book.py @@ -48,6 +48,25 @@ class CbInvTestCase(object): self.assertEqual(book.quantity_digits, 4) self.assertEqual(book.show_performance, True) + # run sorter + books = Book.search( + [], + order=[ + ('current_value', 'ASC'), + ('purchase_amount', 'ASC'), + ('diff_amount', 'ASC'), + ('yield_balance', 'ASC'), + ('diff_percent', 'ASC'), + ('quantity', 'ASC'), + ('quantity_all', 'ASC'), + ('yield_sales', 'ASC'), + ('yield_sales_12m', 'ASC'), + ('yield_dividend_total', 'ASC'), + ('yield_dividend_12m', 'ASC'), + ('yield_fee_total', 'ASC'), + ('yield_fee_12m', 'ASC'), + ]) + @with_transaction() def test_assetbook_aggregated_values(self): """ create cashbooks with hierarchy, add lines, From 38349c989a94d925036d00dca2d33e2351f3cedc Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 31 Dec 2023 10:54:43 +0100 Subject: [PATCH 9/9] Version 7.0.12 --- README.rst | 6 ++++++ tryton.cfg | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9813f32..bd93cfb 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,12 @@ You can monitor trading fees, dividends and sales profits. Changes ======= +*7.0.12 - 31.12.2023* + +- remove caching +- add worker-based precalculation of cashbook-values +- add columns to cashbook list/tree view + *7.0.11 - 06.12.2023* - compatibility to Tryton 7.0 diff --git a/tryton.cfg b/tryton.cfg index 2240a03..9bc5f69 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=7.0.11 +version=7.0.12 depends: cashbook investment