Compare commits

...

9 commits

Author SHA1 Message Date
Frederik Jaeckel
38349c989a Version 7.0.12 2023-12-31 10:54:43 +01:00
Frederik Jaeckel
aefea56353 add sorting 2023-12-30 11:44:33 +01:00
Frederik Jaeckel
9f5c7c6b7d add columns to cashbook tree/list-view 2023-12-30 11:18:49 +01:00
Frederik Jaeckel
8ec7026259 add update of cashbooks on update of asset-rates 2023-12-30 10:57:17 +01:00
Frederik Jaeckel
8c2fb8ed7c cashbook: optimize for speed for checking rows 2023-12-29 23:09:00 +01:00
Frederik Jaeckel
246f035417 add worker-based precalculation of cashbook-values 2023-12-29 23:07:39 +01:00
Frederik Jaeckel
0bc55b8076 remove caching 2023-12-23 10:44:55 +01:00
Frederik Jaeckel
3ea35ea1e3 Etikett ver 7.0.11 zum Änderungssatz 5547a976f370 hinzugefügt 2023-12-06 21:35:28 +01:00
Frederik Jaeckel
7099289345 Version 7.0.11 2023-12-06 21:35:12 +01:00
13 changed files with 788 additions and 147 deletions

View file

@ -23,6 +23,12 @@ You can monitor trading fees, dividends and sales profits.
Changes Changes
======= =======
*7.0.0 - 06.12.2023* *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 - compatibility to Tryton 7.0

View file

@ -11,6 +11,7 @@ from .line import Line
from .splitline import SplitLine from .splitline import SplitLine
from .assetsetting import AssetSetting from .assetsetting import AssetSetting
from .asset import AssetRate from .asset import AssetRate
from .valuestore import ValueStore
def register(): def register():
@ -22,4 +23,5 @@ def register():
SplitLine, SplitLine,
Reconciliation, Reconciliation,
AssetSetting, AssetSetting,
ValueStore,
module='cashbook_investment', type_='model') module='cashbook_investment', type_='model')

View file

@ -3,8 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the # The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms. # full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta from trytond.pool import PoolMeta, Pool
CACHEKEY_ASSETRATE = 'assetrate-%s'
class AssetRate(metaclass=PoolMeta): class AssetRate(metaclass=PoolMeta):
@ -14,34 +13,52 @@ class AssetRate(metaclass=PoolMeta):
def create(cls, vlist): def create(cls, vlist):
""" update cache-value """ 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) 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 return records
@classmethod @classmethod
def write(cls, *args): def write(cls, *args):
""" update cache-value """ 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) super(AssetRate, cls).write(*args)
actions = iter(args) ValueStore.update_books(
for rates, values in zip(actions, actions): ValueStore.get_book_by_books(
for rate in rates: Cashbook.search([
MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate) ('asset', 'in', [
x.asset.id for x in all_rates])])))
@classmethod @classmethod
def delete(cls, records): def delete(cls, records):
""" set cache to None """ 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) super(AssetRate, cls).delete(records)
ValueStore.update_books(books)
# end # end

View file

@ -5,7 +5,6 @@
from trytond.model import ModelSingleton, ModelView, ModelSQL, fields from trytond.model import ModelSingleton, ModelView, ModelSQL, fields
from trytond.pool import Pool
class AssetSetting(ModelSingleton, ModelSQL, ModelView): class AssetSetting(ModelSingleton, ModelSQL, ModelView):
@ -25,13 +24,4 @@ class AssetSetting(ModelSingleton, ModelSQL, ModelView):
model_name='cashbook.book', ondelete='RESTRICT', model_name='cashbook.book', ondelete='RESTRICT',
help='Profit and loss on sale of assets are recorded in the cash book.') 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 # end AssetSetting

367
book.py
View file

@ -5,37 +5,20 @@
from trytond.model import fields, SymbolMixin, Index from trytond.model import fields, SymbolMixin, Index
from trytond.pool import PoolMeta, Pool 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.modules.cashbook.book import STATES2, DEPENDS2
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.report import Report 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 decimal import Decimal
from datetime import timedelta from datetime import timedelta
from sql import Literal from sql import Literal
from sql.functions import CurrentDate from sql.functions import CurrentDate
from sql.aggregate import Sum from sql.aggregate import Sum
from sql.conditionals import Case, Coalesce from sql.conditionals import Case, Coalesce
from trytond.modules.cashbook.model import sub_ids_hierarchical,\ from trytond.modules.cashbook.model import (
CACHEKEY_CURRENCY, AnyInArray sub_ids_hierarchical, 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
class Book(SymbolMixin, metaclass=PoolMeta): class Book(SymbolMixin, metaclass=PoolMeta):
@ -49,8 +32,8 @@ class Book(SymbolMixin, metaclass=PoolMeta):
'invisible': Eval('feature', '') != 'asset', 'invisible': Eval('feature', '') != 'asset',
'readonly': Or( 'readonly': Or(
STATES2['readonly'], STATES2['readonly'],
Len(Eval('lines')) > 0)}, Eval('has_lines', False))},
depends=DEPENDS2+['feature', 'lines']) depends=DEPENDS2+['feature', 'has_lines'])
quantity_digits = fields.Integer( quantity_digits = fields.Integer(
string='Digits', help='Quantity Digits', string='Digits', help='Quantity Digits',
domain=[ domain=[
@ -61,8 +44,8 @@ class Book(SymbolMixin, metaclass=PoolMeta):
'invisible': Eval('feature', '') != 'asset', 'invisible': Eval('feature', '') != 'asset',
'readonly': Or( 'readonly': Or(
STATES2['readonly'], STATES2['readonly'],
Len(Eval('lines')) > 0)}, Eval('has_lines', False))},
depends=DEPENDS2+['feature', 'lines']) depends=DEPENDS2+['feature', 'has_lines'])
asset_uomcat = fields.Function(fields.Many2One( asset_uomcat = fields.Function(fields.Many2One(
string='UOM Category', readonly=True, string='UOM Category', readonly=True,
model_name='product.uom.category', model_name='product.uom.category',
@ -76,8 +59,8 @@ class Book(SymbolMixin, metaclass=PoolMeta):
'invisible': Eval('feature', '') != 'asset', 'invisible': Eval('feature', '') != 'asset',
'readonly': Or( 'readonly': Or(
STATES2['readonly'], STATES2['readonly'],
Len(Eval('lines')) > 0)}, Eval('has_lines', False))},
depends=DEPENDS2+['feature', 'lines', 'asset_uomcat']) depends=DEPENDS2+['feature', 'asset_uomcat', 'has_lines'])
symbol = fields.Function(fields.Char( symbol = fields.Function(fields.Char(
string='Symbol', readonly=True), 'on_change_with_symbol') string='Symbol', readonly=True), 'on_change_with_symbol')
asset_symbol = fields.Function(fields.Many2One( asset_symbol = fields.Function(fields.Many2One(
@ -87,12 +70,14 @@ class Book(SymbolMixin, metaclass=PoolMeta):
string='Quantity', help='Quantity of assets until to date', string='Quantity', help='Quantity of assets until to date',
readonly=True, digits=(16, Eval('quantity_digits', 4)), readonly=True, digits=(16, Eval('quantity_digits', 4)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( quantity_all = fields.Function(fields.Numeric(
string='Total Quantity', help='Total quantity of all assets', string='Total Quantity', help='Total quantity of all assets',
readonly=True, digits=(16, Eval('quantity_digits', 4)), readonly=True, digits=(16, Eval('quantity_digits', 4)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( current_value = fields.Function(fields.Numeric(
string='Value', string='Value',
help='Valuation of the investment based on the current ' + help='Valuation of the investment based on the current ' +
@ -100,7 +85,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': ~Eval('show_performance', False)}, states={'invisible': ~Eval('show_performance', False)},
depends=['currency_digits', 'show_performance']), depends=['currency_digits', 'show_performance']),
'get_asset_quantity') 'get_asset_quantity', searcher='search_asset_quantity')
current_value_ref = fields.Function(fields.Numeric( current_value_ref = fields.Function(fields.Numeric(
string='Value (Ref.)', string='Value (Ref.)',
help='Valuation of the investment based on the current stock' + help='Valuation of the investment based on the current stock' +
@ -111,7 +96,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
~Eval('show_performance', False), ~Eval('show_performance', False),
~Bool(Eval('company_currency', -1)))}, ~Bool(Eval('company_currency', -1)))},
depends=['currency_digits', 'show_performance', 'company_currency']), depends=['currency_digits', 'show_performance', 'company_currency']),
'get_asset_quantity') 'get_asset_quantity', searcher='search_asset_quantity')
# performance # performance
diff_amount = fields.Function(fields.Numeric( diff_amount = fields.Function(fields.Numeric(
@ -120,14 +105,14 @@ class Book(SymbolMixin, metaclass=PoolMeta):
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': ~Eval('show_performance', False)}, states={'invisible': ~Eval('show_performance', False)},
depends=['currency_digits', 'show_performance']), depends=['currency_digits', 'show_performance']),
'get_asset_quantity') 'get_asset_quantity', searcher='search_asset_quantity')
diff_percent = fields.Function(fields.Numeric( diff_percent = fields.Function(fields.Numeric(
string='Percent', string='Percent',
help='percentage performance since acquisition', help='percentage performance since acquisition',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': ~Eval('show_performance', False)}, states={'invisible': ~Eval('show_performance', False)},
depends=['currency_digits', 'show_performance']), depends=['currency_digits', 'show_performance']),
'get_asset_quantity') 'get_asset_quantity', searcher='search_asset_quantity')
show_performance = fields.Function(fields.Boolean( 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( current_rate = fields.Function(fields.Numeric(
@ -136,54 +121,63 @@ class Book(SymbolMixin, metaclass=PoolMeta):
'exchange price.', 'exchange price.',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( purchase_amount = fields.Function(fields.Numeric(
string='Purchase Amount', string='Purchase Amount',
help='Total purchase amount, from shares and fees.', help='Total purchase amount, from shares and fees.',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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
yield_dividend_total = fields.Function(fields.Numeric( yield_dividend_total = fields.Function(fields.Numeric(
string='Dividend', help='Total dividends received', string='Dividend', help='Total dividends received',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_dividend_12m = fields.Function(fields.Numeric(
string='Dividend 1y', string='Dividend 1y',
help='Dividends received in the last twelve months', help='Dividends received in the last twelve months',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_fee_total = fields.Function(fields.Numeric(
string='Trade Fee', help='Total trade fees payed', string='Trade Fee', help='Total trade fees payed',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_fee_12m = fields.Function(fields.Numeric(
string='Trade Fee 1y', string='Trade Fee 1y',
help='Trade fees payed in the last twelve month', help='Trade fees payed in the last twelve month',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_sales = fields.Function(fields.Numeric(
string='Sales', help='Total profit or loss on sale of shares.', string='Sales', help='Total profit or loss on sale of shares.',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_sales_12m = fields.Function(fields.Numeric(
string='Sales 1y', string='Sales 1y',
help='Total profit or loss on sale of shares in the last twelve month.', help='Total profit or loss on sale of shares in the last twelve month.',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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( yield_balance = fields.Function(fields.Numeric(
string='Total Yield', string='Total Yield',
help='Total income: price gain + dividends + sales gains - fees', help='Total income: price gain + dividends + sales gains - fees',
readonly=True, digits=(16, Eval('currency_digits', 2)), readonly=True, digits=(16, Eval('currency_digits', 2)),
states={'invisible': Eval('feature', '') != 'asset'}, 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 @classmethod
def __setup__(cls): def __setup__(cls):
@ -221,6 +215,109 @@ class Book(SymbolMixin, metaclass=PoolMeta):
} }
return recname 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') @fields.depends('asset', 'quantity_uom')
def on_change_asset(self): def on_change_asset(self):
""" get uom from asset """ get uom from asset
@ -287,11 +384,29 @@ class Book(SymbolMixin, metaclass=PoolMeta):
@classmethod @classmethod
def get_yield_data(cls, cashbooks, names): 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 """ collect yield data
""" """
pool = Pool() pool = Pool()
IrDate = pool.get('ir.date') IrDate = pool.get('ir.date')
MemCache = pool.get('cashbook.memcache')
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
context = Transaction().context context = Transaction().context
result = { result = {
@ -309,40 +424,13 @@ class Book(SymbolMixin, metaclass=PoolMeta):
).quantize(Decimal(str(1/10 ** digits))) ).quantize(Decimal(str(1/10 ** digits)))
query_date = context.get('date', IrDate.today()) 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' # results for 'total'
records_total = [] records_total = []
records_12m = [] records_12m = []
if len(todo_cashbook) > 0: if cashbooks:
(tab_book1, query_total) = cls.get_yield_data_sql() (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) cursor.execute(*query_total)
records_total = cursor.fetchall() records_total = cursor.fetchall()
@ -351,7 +439,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
date_to=query_date, date_to=query_date,
date_from=query_date - timedelta(days=365), 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) cursor.execute(*query_12m)
records_12m = cursor.fetchall() records_12m = cursor.fetchall()
@ -370,9 +458,6 @@ class Book(SymbolMixin, metaclass=PoolMeta):
record[2], record[4]) record[2], record[4])
result['yield_sales_12m'][record[0]] = quantize_val( result['yield_sales_12m'][record[0]] = quantize_val(
record[3], record[4]) record[3], record[4])
# store to cache
MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook)
return {x: result[x] for x in names} return {x: result[x] for x in names}
@classmethod @classmethod
@ -395,7 +480,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
(tab_line_yield, query_yield) = Line.get_yield_data_sql() (tab_line_yield, query_yield) = Line.get_yield_data_sql()
context = Transaction().context context = Transaction().context
query_date = context.get('qdate', CurrentDate()) query_date = context.get('date', CurrentDate())
query = tab_book.join( query = tab_book.join(
tab_line, tab_line,
condition=(tab_book.id == tab_line.cashbook), condition=(tab_book.id == tab_line.cashbook),
@ -447,7 +532,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
return (query, tab_book) return (query, tab_book)
@classmethod @classmethod
def get_asset_quantity(cls, cashbooks, names): def get_asset_quantity_values(cls, cashbooks, names):
""" get quantities """ get quantities
field: quantity, quantity_all, current_value, field: quantity, quantity_all, current_value,
current_value_ref, diff_amount, diff_percent, current_value_ref, diff_amount, diff_percent,
@ -457,10 +542,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
CBook = pool.get('cashbook.book') CBook = pool.get('cashbook.book')
Uom = pool.get('product.uom') Uom = pool.get('product.uom')
Currency = pool.get('currency.currency') Currency = pool.get('currency.currency')
IrDate = pool.get('ir.date')
MemCache = pool.get('cashbook.memcache')
cursor = Transaction().connection.cursor() cursor = Transaction().connection.cursor()
context = Transaction().context
(query, tab_book) = cls.get_asset_quantity_sql() (query, tab_book) = cls.get_asset_quantity_sql()
company_currency = CBook.default_currency() company_currency = CBook.default_currency()
@ -473,37 +555,6 @@ class Book(SymbolMixin, metaclass=PoolMeta):
'digits'] '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): def values_from_record(rdata):
""" compute values for record """ compute values for record
""" """
@ -548,8 +599,15 @@ class Book(SymbolMixin, metaclass=PoolMeta):
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_assetbooks = []
ids_nonebtypes = [x.id for x in cashbooks if x.btype is None] 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 # get values of asset-cashbooks in 'cashbooks' of type=asset
if len(ids_assetbooks) > 0: if len(ids_assetbooks) > 0:
@ -644,11 +702,84 @@ class Book(SymbolMixin, metaclass=PoolMeta):
Decimal('100.0') * c_val / p_amount - Decimal('100.0') Decimal('100.0') * c_val / p_amount - Decimal('100.0')
).quantize(Decimal(str(1/10 ** digits))) ).quantize(Decimal(str(1/10 ** digits)))
result['digits'][id_book] = None 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} 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') @fields.depends('id')
def on_change_with_show_performance(self, name=None): def on_change_with_show_performance(self, name=None):
""" return True if current or subordered cashbooks """ return True if current or subordered cashbooks

View file

@ -48,6 +48,25 @@ class CbInvTestCase(object):
self.assertEqual(book.quantity_digits, 4) self.assertEqual(book.quantity_digits, 4)
self.assertEqual(book.show_performance, True) 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() @with_transaction()
def test_assetbook_aggregated_values(self): def test_assetbook_aggregated_values(self):
""" create cashbooks with hierarchy, add lines, """ 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_amount, Decimal('-5.49'))
self.assertEqual(books[0].diff_percent, Decimal('-18.74')) 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(len(books[0].childs), 4)
self.assertEqual( self.assertEqual(
@ -327,10 +364,11 @@ class CbInvTestCase(object):
# wf --> check # wf --> check
Line.wfcheck(book.lines) Line.wfcheck(book.lines)
self.prep_valstore_run_worker()
# check quantities at cashbook # check quantities at cashbook
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 5, 5)}): 'date': date(2022, 5, 5)}):
book2, = Book.browse([book]) book2, = Book.browse([book])
self.assertEqual(book.asset.rate, Decimal('2.8')) # usd self.assertEqual(book.asset.rate, Decimal('2.8')) # usd
self.assertEqual(book2.quantity, Decimal('1.453')) self.assertEqual(book2.quantity, Decimal('1.453'))
@ -340,7 +378,7 @@ class CbInvTestCase(object):
self.assertEqual(book2.current_value_ref, Decimal('3.87')) self.assertEqual(book2.current_value_ref, Decimal('3.87'))
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 5, 12)}): 'date': date(2022, 5, 12)}):
book2, = Book.browse([book]) book2, = Book.browse([book])
self.assertEqual(book2.quantity, Decimal('4.753')) self.assertEqual(book2.quantity, Decimal('4.753'))
self.assertEqual(book2.quantity_all, 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') 'Aurum | 1,750.0000 usd/oz | 05/01/2022')
(usd, euro) = self.prep_2nd_currency(company) (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(company.currency.rec_name, 'Euro')
self.assertEqual(asset.symbol, 'usd/oz') self.assertEqual(asset.symbol, 'usd/oz')
@ -441,7 +490,7 @@ class CbInvTestCase(object):
# check quantities at cashbook # check quantities at cashbook
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 5, 1)}): 'date': date(2022, 5, 1)}):
book2, = Book.browse([book]) book2, = Book.browse([book])
self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd
self.assertEqual(book2.quantity, Decimal('20.0')) self.assertEqual(book2.quantity, Decimal('20.0'))
@ -508,6 +557,16 @@ class CbInvTestCase(object):
'Aurum | 1,750.0000 usd/oz | 05/01/2022') 'Aurum | 1,750.0000 usd/oz | 05/01/2022')
(usd, euro) = self.prep_2nd_currency(company) (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(company.currency.rec_name, 'Euro')
self.assertEqual(asset.symbol, 'usd/oz') self.assertEqual(asset.symbol, 'usd/oz')
chf, = Currency.create([{ chf, = Currency.create([{
@ -562,7 +621,7 @@ class CbInvTestCase(object):
# check quantities at cashbook # check quantities at cashbook
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 5, 1)}): 'date': date(2022, 5, 1)}):
book2, = Book.browse([book]) book2, = Book.browse([book])
self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd
self.assertEqual(book2.quantity, Decimal('20.0')) self.assertEqual(book2.quantity, Decimal('20.0'))
@ -924,6 +983,7 @@ class CbInvTestCase(object):
# set line to 'checked', this creates the counterpart # set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines)) Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open')
self.assertEqual(len(book.lines), 1) self.assertEqual(len(book.lines), 1)
@ -1069,6 +1129,7 @@ class CbInvTestCase(object):
self.assertEqual(len(book2.lines), 0) self.assertEqual(len(book2.lines), 0)
Line.wfcheck(list(book1.lines)) Line.wfcheck(list(book1.lines))
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book1.rec_name, book1.rec_name,
@ -1167,6 +1228,7 @@ class CbInvTestCase(object):
# set line to 'checked', this creates the counterpart # set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines)) Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open')
self.assertEqual(len(book.lines), 1) self.assertEqual(len(book.lines), 1)
@ -1314,6 +1376,7 @@ class CbInvTestCase(object):
# set line to 'checked', this creates the counterpart # set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines)) Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book.rec_name, book.rec_name,
@ -1498,6 +1561,7 @@ class CbInvTestCase(object):
# set line to 'checked', this creates the counterpart # set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines)) Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book.rec_name, book.rec_name,
@ -1855,6 +1919,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]]) Line.wfcheck([books[0].lines[0]])
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
books[0].rec_name, books[0].rec_name,
@ -2010,6 +2075,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]]) Line.wfcheck([books[0].lines[0]])
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
books[0].rec_name, books[0].rec_name,
@ -2192,6 +2258,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]]) Line.wfcheck([books[0].lines[0]])
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
books[0].rec_name, books[0].rec_name,
@ -2531,6 +2598,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]]) Line.wfcheck([books[0].lines[0]])
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
books[0].rec_name, books[0].rec_name,
@ -2690,6 +2758,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].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].rec_name, 'Book Cash | -11.00 usd | Open')
self.assertEqual(books[0].balance_all, Decimal('-11.0')) self.assertEqual(books[0].balance_all, Decimal('-11.0'))
@ -2882,6 +2951,7 @@ class CbInvTestCase(object):
self.assertEqual(len(books[1].lines), 0) self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]]) Line.wfcheck([books[0].lines[0]])
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
books[0].rec_name, books[0].rec_name,

View file

@ -10,9 +10,11 @@ from trytond.modules.investment.tests.test_module import InvestmentTestCase
from .yieldtest import YieldTestCase from .yieldtest import YieldTestCase
from .book import CbInvTestCase from .book import CbInvTestCase
from .reconciliation import ReconTestCase from .reconciliation import ReconTestCase
from .valuestore import ValueStoreTestCase
class CashbookInvestmentTestCase( class CashbookInvestmentTestCase(
ValueStoreTestCase,
CashbookTestCase, CashbookTestCase,
InvestmentTestCase, InvestmentTestCase,
CbInvTestCase, CbInvTestCase,

365
tests/valuestore.py Normal file
View file

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

View file

@ -152,6 +152,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 1) self.assertEqual(len(lines), 1)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -257,6 +258,7 @@ class YieldTestCase(object):
}]) }])
Line.wfcheck(book_cash.lines) Line.wfcheck(book_cash.lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book_asset.rec_name, book_asset.rec_name,
@ -366,6 +368,7 @@ class YieldTestCase(object):
}]) }])
Line.wfcheck(book_asset.lines) Line.wfcheck(book_asset.lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book_asset.rec_name, book_asset.rec_name,
@ -451,6 +454,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 1) self.assertEqual(len(lines), 1)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -532,6 +536,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 1) self.assertEqual(len(lines), 1)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -631,6 +636,7 @@ class YieldTestCase(object):
}]) }])
Line.wfcheck(book_cash.lines) Line.wfcheck(book_cash.lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book_asset.rec_name, book_asset.rec_name,
@ -743,6 +749,7 @@ class YieldTestCase(object):
}]) }])
Line.wfcheck(book_asset.lines) Line.wfcheck(book_asset.lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
book_asset.rec_name, book_asset.rec_name,
@ -879,6 +886,7 @@ class YieldTestCase(object):
lines[0].rec_name, lines[0].rec_name,
'05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u') '05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u')
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -1007,6 +1015,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 2) self.assertEqual(len(lines), 2)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -1150,6 +1159,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 1) self.assertEqual(len(lines), 1)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -1301,6 +1311,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 3) self.assertEqual(len(lines), 3)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,
@ -1438,6 +1449,7 @@ class YieldTestCase(object):
self.assertEqual(len(lines), 2) self.assertEqual(len(lines), 2)
Line.wfcheck(lines) Line.wfcheck(lines)
self.prep_valstore_run_worker()
self.assertEqual( self.assertEqual(
lines[0].rec_name, lines[0].rec_name,

View file

@ -1,5 +1,5 @@
[tryton] [tryton]
version=7.0.0 version=7.0.12
depends: depends:
cashbook cashbook
investment investment

26
valuestore.py Normal file
View file

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

View file

@ -6,9 +6,18 @@ full copyright notices and license terms. -->
<xpath expr="/tree/field[@name='balance']" position="after"> <xpath expr="/tree/field[@name='balance']" position="after">
<field name="current_value" symbol="currency" optional="0"/> <field name="current_value" symbol="currency" optional="0"/>
<field name="purchase_amount" symbol="currency" optional="1"/>
<field name="diff_amount" symbol="currency" optional="0"/> <field name="diff_amount" symbol="currency" optional="0"/>
<field name="yield_balance" symbol="currency" optional="1"/>
<field name="diff_percent" optional="0"/> <field name="diff_percent" optional="0"/>
<field name="quantity" symbol="quantity_uom" optional="0"/> <field name="quantity" symbol="quantity_uom" optional="0"/>
<field name="quantity_all" symbol="quantity_uom" optional="1"/>
<field name="yield_sales" symbol="currency" optional="1"/>
<field name="yield_sales_12m" symbol="currency" optional="1"/>
<field name="yield_dividend_total" symbol="currency" optional="1"/>
<field name="yield_dividend_12m" symbol="currency" optional="1"/>
<field name="yield_fee_total" symbol="currency" optional="1"/>
<field name="yield_fee_12m" symbol="currency" optional="1"/>
</xpath> </xpath>
</data> </data>

View file

@ -5,8 +5,19 @@ full copyright notices and license terms. -->
<data> <data>
<xpath expr="/tree/field[@name='balance']" position="after"> <xpath expr="/tree/field[@name='balance']" position="after">
<field name="current_value" symbol="currency" optional="1"/>
<field name="purchase_amount" symbol="currency" optional="1"/>
<field name="diff_amount" symbol="currency" optional="0"/> <field name="diff_amount" symbol="currency" optional="0"/>
<field name="yield_balance" symbol="currency" optional="1"/>
<field name="diff_percent" optional="0"/> <field name="diff_percent" optional="0"/>
<field name="quantity" symbol="quantity_uom" optional="1"/>
<field name="quantity_all" symbol="quantity_uom" optional="1"/>
<field name="yield_sales" symbol="currency" optional="1"/>
<field name="yield_sales_12m" symbol="currency" optional="1"/>
<field name="yield_dividend_total" symbol="currency" optional="1"/>
<field name="yield_dividend_12m" symbol="currency" optional="1"/>
<field name="yield_fee_total" symbol="currency" optional="1"/>
<field name="yield_fee_12m" symbol="currency" optional="1"/>
</xpath> </xpath>
</data> </data>