diff --git a/README.rst b/README.rst index 75c2884..0ad9bc9 100644 --- a/README.rst +++ b/README.rst @@ -23,6 +23,16 @@ You can monitor trading fees, dividends and sales profits. Changes ======= +*6.8.12 - 31.12.2023* + +- remove caching +- add worker-based precalculation of cashbook-values +- add columns to cashbook list/tree view + +*6.8.11 - 06.12.2023* + +- add: columns optional + *6.8.10 - 08.06.2023* - compatibility to Tryton 6.8 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/asset.py b/asset.py index f805724..83ba202 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, Pool class AssetRate(metaclass=PoolMeta): @@ -14,34 +13,52 @@ class AssetRate(metaclass=PoolMeta): def create(cls, vlist): """ update cache-value """ - MemCache = Pool().get('cashbook.memcache') + pool = Pool() + Cashbook = pool.get('cashbook.book') + ValueStore = pool.get('cashbook.values') records = super(AssetRate, cls).create(vlist) - for rate in records: - MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate) + + 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 """ - MemCache = Pool().get('cashbook.memcache') + 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) - actions = iter(args) - for rates, values in zip(actions, actions): - for rate in rates: - MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate) + 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 """ - MemCache = Pool().get('cashbook.memcache') + 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])])) - for record in records: - MemCache.record_update(CACHEKEY_ASSETRATE % record.asset.id, None) super(AssetRate, cls).delete(records) + ValueStore.update_books(books) # 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 04f2ceb..9e03972 100644 --- a/book.py +++ b/book.py @@ -5,37 +5,20 @@ 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.config import config +from trytond.exceptions import UserError +from trytond.i18n import gettext 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): @@ -49,23 +32,20 @@ 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=[ ('quantity_digits', '>=', 0), - ('quantity_digits', '<=', 6), - ], + ('quantity_digits', '<=', 6)], states={ 'required': Eval('feature', '') == 'asset', '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', @@ -73,17 +53,14 @@ class Book(SymbolMixin, metaclass=PoolMeta): quantity_uom = fields.Many2One( string='UOM', model_name='product.uom', ondelete='RESTRICT', - domain=[ - ('category.id', '=', Eval('asset_uomcat', -1)), - ], + domain=[('category.id', '=', Eval('asset_uomcat', -1))], states={ 'required': Eval('feature', '') == 'asset', 'invisible': Eval('feature', '') != 'asset', 'readonly': Or( - STATES2['readonly'], - Len(Eval('lines')) > 0, - ), - }, depends=DEPENDS2+['feature', 'lines', 'asset_uomcat']) + STATES2['readonly'], + 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( @@ -92,26 +69,23 @@ class Book(SymbolMixin, metaclass=PoolMeta): quantity = fields.Function(fields.Numeric( 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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 ' + 'stock market price.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('show_performance', False) == False, - }, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + states={'invisible': ~Eval('show_performance', False)}, + depends=['currency_digits', 'show_performance']), + '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' + @@ -119,97 +93,91 @@ class Book(SymbolMixin, metaclass=PoolMeta): readonly=True, digits=(16, Eval('currency_digits', 2)), states={ 'invisible': Or( - Eval('show_performance', False) == False, - ~Bool(Eval('company_currency', -1)), - ), - }, depends=['currency_digits', 'show_performance', 'company_currency']), - 'get_asset_quantity') + ~Eval('show_performance', False), + ~Bool(Eval('company_currency', -1)))}, + depends=['currency_digits', 'show_performance', 'company_currency']), + 'get_asset_quantity', searcher='search_asset_quantity') # performance diff_amount = fields.Function(fields.Numeric( string='Difference', help='Difference between acquisition value and current value', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('show_performance', False) == False, - }, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + states={'invisible': ~Eval('show_performance', False)}, + depends=['currency_digits', 'show_performance']), + '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) == False, - }, depends=['currency_digits', 'show_performance']), - 'get_asset_quantity') + states={'invisible': ~Eval('show_performance', False)}, + depends=['currency_digits', 'show_performance']), + 'get_asset_quantity', searcher='search_asset_quantity') show_performance = fields.Function(fields.Boolean( - string='Performance', readonly=True), - 'on_change_with_show_performance') + string='Performance', readonly=True), 'on_change_with_show_performance') current_rate = fields.Function(fields.Numeric( string='Rate', help='Rate per unit of investment based on current stock ' + 'exchange price.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), 'get_asset_quantity') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), + 'get_asset_quantity', searcher='search_asset_quantity') # yield yield_dividend_total = fields.Function(fields.Numeric( 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + 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') + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), + 'on_change_with_yield_balance', searcher='search_asset_quantity') @classmethod def __setup__(cls): @@ -228,7 +196,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): def view_attributes(cls): return super(Book, cls).view_attributes() + [ ('/tree', 'visual', - If(Eval('show_performance', False) == True, + If(Eval('show_performance', False), If(Eval('diff_percent', 0) < 0, 'danger', If(Eval('diff_percent', 0) > 0, 'success', '')), '')), @@ -247,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 @@ -308,17 +379,34 @@ class Book(SymbolMixin, metaclass=PoolMeta): Sum(tab_line_gainloss.gainloss).as_('gainloss'), tab_cur.digits.as_('currency_digits'), group_by=[tab_book.id, tab_cur.digits], - where=where - ) + where=where) return (tab_book, query) @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() IrDate = pool.get('ir.date') - MemCache = pool.get('cashbook.memcache') cursor = Transaction().connection.cursor() context = Transaction().context result = { @@ -336,40 +424,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() @@ -378,7 +439,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() @@ -397,9 +458,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 @@ -422,7 +480,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), @@ -470,12 +528,11 @@ class Book(SymbolMixin, metaclass=PoolMeta): tab_book.currency, tab_cur.digits, tab_asset.uom, tab_book.quantity_uom, tab_asset.currency, tab_balance.balance], - where=(tab_type.feature == 'asset'), - ) + where=(tab_type.feature == 'asset')) 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, @@ -485,10 +542,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() @@ -501,37 +555,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 """ @@ -542,14 +565,12 @@ class Book(SymbolMixin, metaclass=PoolMeta): uom_factor = Decimal( Uom.compute_qty( Uom(rdata[6]), 1.0, - Uom(rdata[7]), round=False) - ) + Uom(rdata[7]), round=False)) current_value = Currency.compute( rdata[8], rdata[3] * rdata[1] / uom_factor, - rdata[4] - ) + rdata[4]) return (record[0], { 'quantity': rdata[1], 'quantity_all': rdata[2], @@ -558,8 +579,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): rdata[8], rdata[3] * rdata[1] / uom_factor, company_currency - if company_currency is not None else rdata[8], - ), + if company_currency is not None else rdata[8]), 'diff_amount': current_value - rdata[9], 'diff_percent': ( Decimal('100.0') * current_value / @@ -576,12 +596,18 @@ class Book(SymbolMixin, metaclass=PoolMeta): rdata[4], record[9], company_currency - if company_currency is not None else rdata[4], - ), + 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: @@ -630,8 +656,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): tab_book.id, tab_quantity.rate, tab_quantity.currency, tab_quantity.currency_digits, tab_quantity.uom, tab_quantity.quantity_uom, - tab_quantity.asset_currency, tab_book.currency], - ) + tab_quantity.asset_currency, tab_book.currency]) cursor.execute(*query) records = cursor.fetchall() @@ -650,16 +675,14 @@ class Book(SymbolMixin, metaclass=PoolMeta): company_currency if company_currency is not None else record[4], values['current_value_ref'], - record[10], - ) + record[10]) elif name == 'diff_amount': value = Currency.compute( company_currency if company_currency is not None else record[4], values['current_value_ref'] - values['purchase_amount_ref'], - record[10], - ) + record[10]) elif name in ['current_value_ref', 'purchase_amount_ref']: value = values[name] result['digits'][book_id] = record[5] @@ -679,11 +702,84 @@ 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} + @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 @@ -710,8 +806,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): """ return '%(currency)s/%(unit)s' % { 'currency': getattr(self.currency, 'symbol', '-'), - 'unit': getattr(self.quantity_uom, 'symbol', '-'), - } + 'unit': getattr(self.quantity_uom, 'symbol', '-')} @fields.depends('asset', '_parent_asset.uom') def on_change_with_asset_uomcat(self, name=None): diff --git a/line.py b/line.py index c46d16c..9497e03 100644 --- a/line.py +++ b/line.py @@ -14,21 +14,19 @@ from trytond.i18n import gettext from trytond.report import Report from trytond.transaction import Transaction from trytond.modules.cashbook.line import STATES, DEPENDS +from trytond.modules.cashbook.const import DEF_NONE from .mixin import SecondUomMixin STATESQ1 = { 'invisible': And( Eval('feature', '') != 'asset', - Eval('booktransf_feature', '') != 'asset', - ), + Eval('booktransf_feature', '') != 'asset'), 'required': Or( Eval('feature', '') == 'asset', - Eval('booktransf_feature', '') == 'asset', - ), + Eval('booktransf_feature', '') == 'asset'), 'readonly': Or( STATES['readonly'], - Eval('bookingtype', '').in_(['spin', 'spout']), - ), + Eval('bookingtype', '').in_(['spin', 'spout'])), } DEPENDSQ1 = ['feature', 'booktransf_feature', 'quantity_digits', 'bookingtype'] DEPENDSQ1.extend(DEPENDS) @@ -38,14 +36,12 @@ STATESQ1B.update(STATESQ1) STATESQ1B['invisible'] = And( Eval('feature', '') != 'asset', Eval('booktransf_feature', '') != 'asset', - Eval('splitline_has_quantity', False) == False, - ) + ~Eval('splitline_has_quantity', False)) STATESQ2 = { 'invisible': Eval('feature', '') != 'asset', - 'required': Eval('feature', '') == 'asset', - } + 'required': Eval('feature', '') == 'asset'} DEPENDSQ2 = ['feature', 'quantity_digits', 'bookingtype'] @@ -66,30 +62,26 @@ class Line(SecondUomMixin, metaclass=PoolMeta): states=STATESQ2, depends=DEPENDSQ2) quantity_digits = fields.Function(fields.Integer( - string='Digits', - readonly=True, states={'invisible': True}), + string='Digits', readonly=True, states={'invisible': True}), 'on_change_with_quantity_digits') quantity_uom = fields.Function(fields.Many2One( - string='Symbol', - readonly=True, model_name='product.uom'), + string='Symbol', readonly=True, model_name='product.uom'), 'on_change_with_quantity_uom') asset_rate = fields.Function(fields.Numeric( string='Rate', readonly=True, digits=(16, If( Eval('currency_digits', 2) > Eval('quantity_digits', 2), Eval('currency_digits', 2), Eval('quantity_digits', 2))), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'quantity_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'quantity_digits', 'feature']), 'on_change_with_asset_rate') quantity_balance = fields.Function(fields.Numeric( string='Quantity', digits=(16, Eval('quantity_digits', 4)), readonly=True, help='Number of shares in the cashbook up to the current ' + 'row if the default sort applies.', - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['quantity_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['quantity_digits', 'feature']), 'on_change_with_quantity_balance') splitline_has_quantity = fields.Function(fields.Boolean( string='has quantity', readonly=True, states={'invisible': True}), @@ -101,50 +93,42 @@ class Line(SecondUomMixin, metaclass=PoolMeta): help='Valuation of the investment based on the current ' + 'stock market price.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'on_change_with_current_value') diff_amount = fields.Function(fields.Numeric( string='Difference', help='Difference between acquisition value and current value', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), - 'on_change_with_diff_amount') + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'on_change_with_diff_amount') diff_percent = fields.Function(fields.Numeric( string='Percent', help='percentage performance since acquisition', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), - 'on_change_with_diff_percent') + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'on_change_with_diff_percent') trade_fee = fields.Function(fields.Numeric( string='Fee', help='Trading fee for the current booking line.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'get_yield_data', searcher='search_trade_fee') asset_dividend = fields.Function(fields.Numeric( string='Dividend', help='Dividend received at the current booking line.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'get_yield_data', searcher='search_asset_dividend') asset_gainloss = fields.Function(fields.Numeric( string='Profit/Loss', help='Profit or loss on sale on the current booking line.', readonly=True, digits=(16, Eval('currency_digits', 2)), - states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), + states={'invisible': Eval('feature', '') != 'asset'}, + depends=['currency_digits', 'feature']), 'get_yield_data', searcher='search_asset_gainloss') @classmethod @@ -176,7 +160,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): tab_mvsp_counterpart, # [MV-SP] transfer booking, # select counterpart [1] - a split-booking - condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \ + condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & ((tab_line.reference == tab_mvsp_counterpart.id) | (tab_line.id == tab_mvsp_counterpart.reference)) & (tab_mvsp_counterpart.bookingtype.in_(['spin', 'spout'])), @@ -185,9 +169,9 @@ class Line(SecondUomMixin, metaclass=PoolMeta): tab_mv_spline, # [MV-SP] line is linked to split-booking-line # of counterpart [1] - condition=(tab_mv_spline.line == tab_mvsp_counterpart.id) & \ - (tab_mv_spline.splittype == 'tr') & \ - (tab_mv_spline.booktransf != None) & \ + condition=(tab_mv_spline.line == tab_mvsp_counterpart.id) & + (tab_mv_spline.splittype == 'tr') & + (tab_mv_spline.booktransf != DEF_NONE) & (tab_mv_spline.booktransf == gainloss_book), type_='LEFT OUTER', @@ -195,20 +179,20 @@ class Line(SecondUomMixin, metaclass=PoolMeta): tab_spmv_counterpart, # [SP-MV] split booking, select counterpart [1] # - a transfer-booking - condition=tab_line.bookingtype.in_(['spin', 'spout']) & \ - ((tab_line.reference == tab_spmv_counterpart.id) | \ - (tab_line.id == tab_spmv_counterpart.reference)) & \ - tab_spmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & \ + condition=tab_line.bookingtype.in_(['spin', 'spout']) & + ((tab_line.reference == tab_spmv_counterpart.id) | + (tab_line.id == tab_spmv_counterpart.reference)) & + tab_spmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & (tab_spmv_counterpart.cashbook == gainloss_book), type_='LEFT OUTER', ).join( tab_mvmv_counterpart, # [MV-MV] transfer booking - condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \ - ((tab_mvmv_counterpart.reference == tab_line.id) | \ - (tab_mvmv_counterpart.id == tab_line.reference)) & \ - tab_mvmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & \ + condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & + ((tab_mvmv_counterpart.reference == tab_line.id) | + (tab_mvmv_counterpart.id == tab_line.reference)) & + tab_mvmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & (tab_mvmv_counterpart.cashbook == gainloss_book), type_='LEFT OUTER', ).select( @@ -218,17 +202,14 @@ class Line(SecondUomMixin, metaclass=PoolMeta): Case( (tab_line.bookingtype == 'mvin', tab_mv_spline.amount), (tab_line.bookingtype == 'mvout', - tab_mv_spline.amount * Decimal('-1.0')), - ), + tab_mv_spline.amount * Decimal('-1.0'))), Case( (tab_mvsp_counterpart.cashbook == gainloss_book, - tab_line.debit - tab_line.credit), - ), + tab_line.debit - tab_line.credit)), tab_spmv_counterpart.credit - tab_spmv_counterpart.debit, Decimal('0.0'), ) * Decimal('-1.0')).as_('gainloss'), - tab_line.cashbook, - ) + tab_line.cashbook) return (tab_line, query) @classmethod @@ -262,17 +243,17 @@ class Line(SecondUomMixin, metaclass=PoolMeta): ).join( tab_inout_fee, # [INOUT] fee, local booked - condition=(tab_inout_fee.id == tab_line.id) & \ - tab_inout_fee.bookingtype.in_(['in', 'out']) & \ - (tab_inout_fee.category != None) & \ + condition=(tab_inout_fee.id == tab_line.id) & + tab_inout_fee.bookingtype.in_(['in', 'out']) & + (tab_inout_fee.category != DEF_NONE) & (tab_inout_fee.category == fee_category), type_='LEFT OUTER', ).join( tab_inout_divi, # [INOUT] dividend, local booked - condition=(tab_inout_divi.id == tab_line.id) & \ - tab_inout_divi.bookingtype.in_(['in', 'out']) & \ - (tab_inout_divi.category != None) & \ + condition=(tab_inout_divi.id == tab_line.id) & + tab_inout_divi.bookingtype.in_(['in', 'out']) & + (tab_inout_divi.category != DEF_NONE) & (tab_inout_divi.category == dividend_category), type_='LEFT OUTER', @@ -280,46 +261,46 @@ class Line(SecondUomMixin, metaclass=PoolMeta): tab_mv_counterpart, # [MV] transfer booking, select counterpart [1] # - a split-booking - condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \ - ((tab_line.reference == tab_mv_counterpart.id) | \ - (tab_line.id == tab_mv_counterpart.reference)) & \ + condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & + ((tab_line.reference == tab_mv_counterpart.id) | + (tab_line.id == tab_mv_counterpart.reference)) & (tab_mv_counterpart.bookingtype.in_(['spin', 'spout'])), type_='LEFT OUTER', ).join( tab_mv_spline_fee, # [MV] fee-line is linked to split-booking-line # of counterpart [1] - condition=(tab_mv_spline_fee.line == tab_mv_counterpart.id) & \ - (tab_mv_spline_fee.splittype == 'cat') & \ - (tab_mv_spline_fee.category != None) & \ + condition=(tab_mv_spline_fee.line == tab_mv_counterpart.id) & + (tab_mv_spline_fee.splittype == 'cat') & + (tab_mv_spline_fee.category != DEF_NONE) & (tab_mv_spline_fee.category == fee_category), type_='LEFT OUTER', ).join( tab_mv_spline_divi, # [MV] dividend-line is linked to split-booking-line # of counterpart [1] - condition=(tab_mv_spline_divi.line == tab_mv_counterpart.id) & \ - (tab_mv_spline_divi.splittype == 'cat') & \ - (tab_mv_spline_divi.category != None) & \ + condition=(tab_mv_spline_divi.line == tab_mv_counterpart.id) & + (tab_mv_spline_divi.splittype == 'cat') & + (tab_mv_spline_divi.category != DEF_NONE) & (tab_mv_spline_divi.category == dividend_category), type_='LEFT OUTER', ).join( tab_spline_fee, # [SP] fee, split booking - condition=(tab_spline_fee.line == tab_line.id) & \ - tab_line.bookingtype.in_(['spin', 'spout']) & \ - (tab_spline_fee.splittype == 'cat') & \ - (tab_spline_fee.category != None) & \ + condition=(tab_spline_fee.line == tab_line.id) & + tab_line.bookingtype.in_(['spin', 'spout']) & + (tab_spline_fee.splittype == 'cat') & + (tab_spline_fee.category != DEF_NONE) & (tab_spline_fee.category == fee_category), type_='LEFT OUTER', ).join( tab_spline_divi, # [SP] dividend, split booking - condition=(tab_spline_divi.line == tab_line.id) & \ - tab_line.bookingtype.in_(['spin', 'spout']) & \ - (tab_spline_divi.splittype == 'cat') & \ - (tab_spline_divi.category != None) & \ + condition=(tab_spline_divi.line == tab_line.id) & + tab_line.bookingtype.in_(['spin', 'spout']) & + (tab_spline_divi.splittype == 'cat') & + (tab_spline_divi.category != DEF_NONE) & (tab_spline_divi.category == dividend_category), type_='LEFT OUTER', ).select( @@ -350,8 +331,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): Decimal('0.0'), )).as_('dividend'), tab_line.cashbook, - group_by=[tab_line.id, tab_line.cashbook], - ) + group_by=[tab_line.id, tab_line.cashbook]) return (tab_line, query) @classmethod @@ -363,8 +343,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): query = tab_query.select( tab_query.id, - where=Operator(tab_query.fee, clause[2]), - ) + where=Operator(tab_query.fee, clause[2])) return [('id', 'in', query)] @classmethod @@ -376,8 +355,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): query = tab_query.select( tab_query.id, - where=Operator(tab_query.dividend, clause[2]), - ) + where=Operator(tab_query.dividend, clause[2])) return [('id', 'in', query)] @classmethod @@ -389,8 +367,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): query = tab_query.select( tab_query.id, - where=Operator(tab_query.gainloss, clause[2]), - ) + where=Operator(tab_query.gainloss, clause[2])) return [('id', 'in', query)] @classmethod @@ -421,8 +398,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): line = Line2(record[0]) values = { 'trade_fee': quantize_val(record[1], line), - 'asset_dividend': quantize_val(record[2], line), - } + 'asset_dividend': quantize_val(record[2], line)} for name in list(name_set): result[name][record[0]] = values[name] @@ -452,8 +428,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): 'quantity': Report.format_number( credit - debit, lang=None, digits=self.quantity_digits), - 'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'), - } + 'uom_symbol': getattr(self.quantity_uom, 'symbol', '-')} return recname @classmethod @@ -516,8 +491,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): result = super(Line, cls).get_counterpart_values( line, splitline=splitline, - values=values - ) + values=values) line_uom = getattr(line.quantity_uom, 'id', None) booktransf_uom = getattr(getattr( @@ -543,27 +517,23 @@ class Line(SecondUomMixin, metaclass=PoolMeta): if (asset_books == 2) and (diff_uom is True) else splitline.quantity, 'quantity_2nd_uom': splitline.quantity - if (asset_books == 2) and (diff_uom is True) else None, - }) + if (asset_books == 2) and (diff_uom is True) else None}) elif sum([1 if booktransf_uom is not None else 0, 1 if line_uom is not None else 0]) == 1: # one of the related cashbooks only is asset-type result.update({ 'quantity': line.quantity, - 'quantity_2nd_uom': None, - }) + 'quantity_2nd_uom': None}) elif sum([1 if booktransf_uom is not None else 0, 1 if line_uom is not None else 0]) == 2: if line_uom == booktransf_uom: result.update({ 'quantity': line.quantity, - 'quantity_2nd_uom': None, - }) + 'quantity_2nd_uom': None}) else: result.update({ 'quantity': line.quantity_2nd_uom, - 'quantity_2nd_uom': line.quantity, - }) + 'quantity_2nd_uom': line.quantity}) return result @fields.depends('amount', 'splitlines', 'quantity') @@ -728,8 +698,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): (line.quantity_debit is None): raise UserError(gettext( 'cashbook_investment.msg_line_quantity_not_set', - linetxt=line.rec_name, - )) + linetxt=line.rec_name)) # quantity and amount must with same sign if (line.amount != Decimal('0.0')) and \ @@ -739,8 +708,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): if amount_sign != quantity_sign: raise UserError(gettext( 'cashbook_investment.msg_line_sign_mismatch', - linetxt=line.rec_name, - )) + linetxt=line.rec_name)) @classmethod def update_values_by_splitlines(cls, lines): @@ -753,7 +721,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): quantity = sum([ x.quantity or Decimal('0.0') for x in line.splitlines]) if (cnt1 > 0) and (quantity != line.quantity): - to_write.extend([[line], {'quantity': quantity, }]) + to_write.extend([[line], {'quantity': quantity}]) return to_write @classmethod diff --git a/mixin.py b/mixin.py index 327e708..bab4563 100644 --- a/mixin.py +++ b/mixin.py @@ -28,12 +28,11 @@ class SecondUomMixin(object): digits=(16, Eval('quantity2nd_digits', 4)), states={ 'readonly': Or( - STATESQ['readonly'], - ~Bool(Eval('quantity2nd')) - ), + STATESQ['readonly'], + ~Bool(Eval('quantity2nd'))), 'required': Bool(Eval('quantity2nd')), - 'invisible': ~Bool(Eval('quantity2nd')), - }, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']) + 'invisible': ~Bool(Eval('quantity2nd'))}, + depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']) factor_2nd_uom = fields.Function(fields.Numeric( string='Conversion factor', help='Conversion factor between the units of the ' + @@ -41,12 +40,11 @@ class SecondUomMixin(object): digits=uom_conversion_digits, states={ 'readonly': Or( - STATESQ['readonly'], - ~Bool(Eval('quantity2nd')) - ), + STATESQ['readonly'], + ~Bool(Eval('quantity2nd'))), 'required': Bool(Eval('quantity2nd')), - 'invisible': ~Bool(Eval('quantity2nd')), - }, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']), + 'invisible': ~Bool(Eval('quantity2nd'))}, + depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']), 'on_change_with_factor_2nd_uom', setter='set_factor_2nd_uom') quantity2nd = fields.Function(fields.Many2One( @@ -86,8 +84,7 @@ class SecondUomMixin(object): raise UserError(gettext( 'cashbook_investment.msg_uomcat_mismatch', cat1=from_uom.category.rec_name, - cat2=booktransf.quantity_uom.category.rec_name, - )) + cat2=booktransf.quantity_uom.category.rec_name)) values['quantity_2nd_uom'] = Decimal(UOM.compute_qty( from_uom, @@ -96,8 +93,7 @@ class SecondUomMixin(object): round=False, )).quantize(Decimal( Decimal(1) / - 10 ** booktransf.quantity_digits) - ) + 10 ** booktransf.quantity_digits)) return values @classmethod @@ -175,8 +171,7 @@ class SecondUomMixin(object): self.quantity_uom, float(self.quantity), self.booktransf.quantity_uom, - round=False, - )) + round=False)) if self.quantity != Decimal('0.0'): self.factor_2nd_uom = ( self.quantity_2nd_uom / self.quantity diff --git a/reconciliation.py b/reconciliation.py index b7b85e3..91f95ab 100644 --- a/reconciliation.py +++ b/reconciliation.py @@ -39,15 +39,18 @@ class Reconciliation(metaclass=PoolMeta): """ recname = super(Reconciliation, self).get_rec_name(name) if self.cashbook.feature == 'asset': - recname += ' | %(start_quantity)s %(uom_symbol)s - %(end_quantity)s %(uom_symbol)s' % { - 'start_quantity': Report.format_number( + recname += ' '.join([ + ' |', + Report.format_number( self.start_quantity or 0.0, None, digits=self.quantity_digits), - 'end_quantity': Report.format_number( + getattr(self.quantity_uom, 'symbol', '-'), + '-', + Report.format_number( self.end_quantity or 0.0, None, digits=self.quantity_digits), - 'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'), - } + getattr(self.quantity_uom, 'symbol', '-') + ]) return recname @fields.depends('cashbook', '_parent_cashbook.quantity_uom') @@ -82,8 +85,7 @@ class Reconciliation(metaclass=PoolMeta): values = super(Reconciliation, cls).get_values_wfedit(reconciliation) values.update({ 'start_quantity': Decimal('0.0'), - 'end_quantity': Decimal('0.0'), - }) + 'end_quantity': Decimal('0.0')}) return values @classmethod @@ -116,8 +118,7 @@ class Reconciliation(metaclass=PoolMeta): # add quantities of already linked lines values['end_quantity'] += sum([ x.quantity_credit - x.quantity_debit - for x in reconciliation.lines - ]) + for x in reconciliation.lines]) return values diff --git a/splitline.py b/splitline.py index 4337e72..3718e45 100644 --- a/splitline.py +++ b/splitline.py @@ -45,8 +45,7 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta): 'quantity': Report.format_number( self.quantity or 0.0, None, digits=self.quantity_digits), - 'uom_symbol': self.quantity_uom.symbol, - } + 'uom_symbol': self.quantity_uom.symbol} return recname @fields.depends( @@ -76,7 +75,6 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta): if self.booktransf: if self.booktransf.feature == 'asset': return self.booktransf.quantity_digits - return 4 @classmethod diff --git a/tests/book.py b/tests/book.py index facab82..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, @@ -174,6 +193,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 +364,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 +378,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 +437,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 +490,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 +557,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,14 +621,16 @@ 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')) self.assertEqual(book2.quantity_all, Decimal('20.0')) # usd --> chf: 1750 US$ * 0.95 / 1.05 = 1583.333 € - # 1 ounce --> 20 gram: 1583.333 CHF * 20 / 28.3495 = 1117.0097 CHF - self.assertEqual(book2.current_value, Decimal('1117.01')) # CHF + # 1 ounce --> 20 gram: + # 1583.333 CHF * 20 / 28.3495 = 1117.0097 CHF + self.assertEqual( + book2.current_value, Decimal('1117.01')) # CHF self.assertEqual( book2.current_value_ref, Decimal('1175.80')) # EUR self.assertEqual(book2.diff_amount, Decimal('-132.99')) @@ -698,7 +759,8 @@ class CbInvTestCase(object): }]) self.assertEqual( books[1].lines[1].rec_name, - '05/01/2022|from|1.00 usd|- [Book 1 | 1.00 usd | Open | 1.000 u]') + '05/01/2022|from|1.00 usd|- [Book 1 | ' + + '1.00 usd | Open | 1.000 u]') self.assertEqual(books[1].lines[1].quantity_digits, 3) @with_transaction() @@ -736,7 +798,8 @@ class CbInvTestCase(object): }]) self.assertEqual(book.name, 'Book 1') - self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 u') + self.assertEqual( + book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 u') self.assertEqual(book.btype.rec_name, 'A - Asset') self.assertEqual(book.state, 'open') self.assertEqual(book.feature, 'asset') @@ -846,7 +909,8 @@ class CbInvTestCase(object): }]) self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset = self.prep_asset_item( @@ -919,6 +983,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) @@ -1009,7 +1074,8 @@ class CbInvTestCase(object): 'feature': 'asset', }]) - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset = self.prep_asset_item( @@ -1063,6 +1129,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, @@ -1100,7 +1167,8 @@ class CbInvTestCase(object): }]) self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset = self.prep_asset_item( @@ -1160,6 +1228,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) @@ -1240,7 +1309,8 @@ class CbInvTestCase(object): }]) self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset = self.prep_asset_item( @@ -1306,6 +1376,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, @@ -1385,7 +1456,8 @@ class CbInvTestCase(object): }]) self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset1 = self.prep_asset_item( @@ -1489,6 +1561,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, @@ -1573,7 +1646,8 @@ class CbInvTestCase(object): }]) self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') + category_out = self.prep_category( + name='Out Category', cattype='out') self.prep_party() asset1 = self.prep_asset_item( @@ -1633,7 +1707,8 @@ class CbInvTestCase(object): self.assertRaisesRegex( UserError, - r'Cannot transfer quantities between cashbooks with different unit-categories \(Time != Weight\).', + r'Cannot transfer quantities between cashbooks with ' + + r'different unit-categories \(Time != Weight\).', Book.write, *[ [book], @@ -1708,7 +1783,8 @@ class CbInvTestCase(object): }])], }]) - self.assertEqual(book.rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') + self.assertEqual( + book.rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') self.assertEqual(book.balance_all, Decimal('11.0')) self.assertEqual(len(book.lines), 1) @@ -1723,10 +1799,12 @@ class CbInvTestCase(object): self.assertEqual(len(book.lines[0].splitlines), 2) self.assertEqual(book.lines[0].splitlines[0].splittype, 'cat') self.assertEqual(book.lines[0].splitlines[0].amount, Decimal('5.0')) - self.assertEqual(book.lines[0].splitlines[0].quantity, Decimal('1.5')) + self.assertEqual( + book.lines[0].splitlines[0].quantity, Decimal('1.5')) self.assertEqual(book.lines[0].splitlines[1].splittype, 'cat') self.assertEqual(book.lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(book.lines[0].splitlines[1].quantity, Decimal('2.5')) + self.assertEqual( + book.lines[0].splitlines[1].quantity, Decimal('2.5')) @with_transaction() def test_assetbook_split_in_category_and_assetbook(self): @@ -1814,7 +1892,8 @@ class CbInvTestCase(object): self.assertEqual(len(books[0].lines[0].splitlines), 2) self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') - self.assertEqual(books[0].lines[0].splitlines[0].amount, Decimal('5.0')) + self.assertEqual( + books[0].lines[0].splitlines[0].amount, Decimal('5.0')) self.assertEqual( books[0].lines[0].splitlines[0].quantity, Decimal('1.5')) @@ -1823,7 +1902,8 @@ class CbInvTestCase(object): 'Cat1') self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) self.assertEqual( books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) @@ -1833,11 +1913,13 @@ class CbInvTestCase(object): self.assertEqual(len(books[0].lines[0].references), 0) self.assertEqual(books[0].lines[0].reference, None) - self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') self.assertEqual(books[1].balance_all, Decimal('0.0')) self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -1855,7 +1937,8 @@ class CbInvTestCase(object): self.assertEqual(len(books[0].lines[0].splitlines), 2) self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') - self.assertEqual(books[0].lines[0].splitlines[0].amount, Decimal('5.0')) + self.assertEqual( + books[0].lines[0].splitlines[0].amount, Decimal('5.0')) self.assertEqual( books[0].lines[0].splitlines[0].quantity, Decimal('1.5')) @@ -1864,7 +1947,8 @@ class CbInvTestCase(object): 'Cat1') self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) self.assertEqual( books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) @@ -1985,11 +2069,13 @@ class CbInvTestCase(object): self.assertEqual(len(books[0].lines[0].references), 0) self.assertEqual(books[0].lines[0].reference, None) - self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') self.assertEqual(books[1].balance_all, Decimal('0.0')) self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2172,6 +2258,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, @@ -2269,7 +2356,8 @@ class CbInvTestCase(object): self.assertEqual( asset1.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - self.assertEqual(asset2.rec_name, 'Liquid | 10.0000 usd/l | 05/01/2022') + self.assertEqual( + asset2.rec_name, 'Liquid | 10.0000 usd/l | 05/01/2022') (usd, euro) = self.prep_2nd_currency(company) self.assertEqual(company.currency.rec_name, 'Euro') @@ -2310,7 +2398,8 @@ class CbInvTestCase(object): self.assertRaisesRegex( UserError, - r"Cannot transfer quantities between cashbooks with different unit-categories \(Weight != Volume\).", + r"Cannot transfer quantities between cashbooks with " + + r"different unit-categories \(Weight != Volume\).", Book.write, *[ [books[0]], @@ -2393,7 +2482,8 @@ class CbInvTestCase(object): }])], }]) - self.assertEqual(book.rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') + self.assertEqual( + book.rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') self.assertEqual(book.balance_all, Decimal('-11.0')) self.assertEqual(len(book.lines), 1) self.assertEqual(book.lines[0].splitline_has_quantity, False) @@ -2502,11 +2592,13 @@ class CbInvTestCase(object): self.assertEqual(len(books[0].lines[0].references), 0) self.assertEqual(books[0].lines[0].reference, None) - self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') self.assertEqual(books[1].balance_all, Decimal('0.0')) self.assertEqual(len(books[1].lines), 0) Line.wfcheck([books[0].lines[0]]) + self.prep_valstore_run_worker() self.assertEqual( books[0].rec_name, @@ -2527,7 +2619,8 @@ class CbInvTestCase(object): books[0].lines[0].splitlines[1].rec_name, 'Exp/Sp|6.00 usd|to cashbook [Book 2 | 6.00 usd | Open' + ' | 2.50 u]|2.50 u') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) self.assertEqual( books[0].lines[0].splitlines[1].amount_2nd_currency, None) @@ -2545,7 +2638,8 @@ class CbInvTestCase(object): ' | Open | -4.00 u]|2.50 u') self.assertEqual(books[0].lines[0].reference, None) - self.assertEqual(books[1].rec_name, 'Book 2 | 6.00 usd | Open | 2.50 u') + self.assertEqual( + books[1].rec_name, 'Book 2 | 6.00 usd | Open | 2.50 u') self.assertEqual(books[1].balance_all, Decimal('6.0')) self.assertEqual(len(books[1].lines), 1) self.assertEqual( @@ -2642,7 +2736,8 @@ class CbInvTestCase(object): books[0].lines[0].splitlines[0].rec_name, 'Exp/Sp|5.00 usd|to category [Cat1]') self.assertEqual(books[0].lines[0].splitlines[0].quantity, None) - self.assertEqual(books[0].lines[0].splitlines[0].quantity_2nd_uom, None) + self.assertEqual( + books[0].lines[0].splitlines[0].quantity_2nd_uom, None) self.assertEqual( books[0].lines[0].splitlines[1].rec_name, 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 0.00 usd | ' + @@ -2650,7 +2745,8 @@ class CbInvTestCase(object): self.assertEqual( books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity_2nd_uom, None) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity_2nd_uom, None) self.assertEqual(len(books[0].lines[0].references), 0) self.assertEqual(books[0].lines[0].reference, None) @@ -2662,6 +2758,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')) @@ -2684,7 +2781,8 @@ class CbInvTestCase(object): books[0].lines[0].splitlines[1].rec_name, 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 6.00 usd' + ' | Open | 2.50 u]') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) self.assertEqual( books[0].lines[0].splitlines[1].amount_2nd_currency, None) @@ -2853,6 +2951,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, @@ -2993,7 +3092,8 @@ class CbInvTestCase(object): self.assertRaisesRegex( UserError, - r"Cannot transfer quantities between cashbooks with different unit-categories \(Weight != Volume\).", + r"Cannot transfer quantities between cashbooks with " + + r"different unit-categories \(Weight != Volume\).", Book.write, *[ [books[0]], 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 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/tryton.cfg b/tryton.cfg index c967ff6..adca862 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.8.10 +version=6.8.12 depends: cashbook investment @@ -11,3 +11,4 @@ xml: splitline.xml assetsetting.xml menu.xml + 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 diff --git a/versiondep.txt b/versiondep.txt index 56d4ce7..5af99ba 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -1 +1 @@ -cashbook;6.8.28;6.8.999;mds +cashbook;6.8.32;6.8.999;mds diff --git a/view/book_list.xml b/view/book_list.xml index 9d04bfd..f874535 100644 --- a/view/book_list.xml +++ b/view/book_list.xml @@ -5,10 +5,19 @@ full copyright notices and license terms. --> - - - - + + + + + + + + + + + + + diff --git a/view/book_tree.xml b/view/book_tree.xml index c375777..ce3eddc 100644 --- a/view/book_tree.xml +++ b/view/book_tree.xml @@ -5,8 +5,19 @@ full copyright notices and license terms. --> - - + + + + + + + + + + + + + diff --git a/view/line_list.xml b/view/line_list.xml index e292813..4c47438 100644 --- a/view/line_list.xml +++ b/view/line_list.xml @@ -5,9 +5,9 @@ full copyright notices and license terms. --> - - - + + + diff --git a/view/recon_list.xml b/view/recon_list.xml index 6fa1694..a80a5fa 100644 --- a/view/recon_list.xml +++ b/view/recon_list.xml @@ -5,7 +5,7 @@ full copyright notices and license terms. --> - - + + diff --git a/view/split_list.xml b/view/split_list.xml index 876e52d..601b4ec 100644 --- a/view/split_list.xml +++ b/view/split_list.xml @@ -5,8 +5,8 @@ full copyright notices and license terms. --> - - + +