diagram: interpolator ergänzt,

asset: berechnung 'nextupdate' korrigiert
This commit is contained in:
Frederik Jaeckel 2022-11-28 23:15:28 +01:00
parent d1421403b1
commit 99e18094aa
7 changed files with 303 additions and 67 deletions

190
asset.py
View file

@ -6,17 +6,22 @@
from trytond.model import ModelView, ModelSQL, fields from trytond.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction from trytond.transaction import Transaction
from trytond.pool import Pool from trytond.pool import Pool
from trytond.pyson import Eval, Bool, And, If from trytond.pyson import Eval, Bool, And, If, Date
from trytond.report import Report from trytond.report import Report
from decimal import Decimal from decimal import Decimal
from datetime import time from datetime import time
from sql.functions import CurrentDate, CurrentTimestamp, Round from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
from sql.conditionals import Case, Coalesce from sql.conditionals import Case, Coalesce
from sql import Literal from sql import Literal
digits_percent = 2 digits_percent = 2
sel_updtdays = [
('work', 'Mon - Fri'),
('week', 'Mon - Sun'),
]
class Asset(ModelSQL, ModelView): class Asset(ModelSQL, ModelView):
'Asset' 'Asset'
@ -45,9 +50,11 @@ class Asset(ModelSQL, ModelView):
model_name='investment.rate') model_name='investment.rate')
rate = fields.Function(fields.Numeric(string='Current Rate', rate = fields.Function(fields.Numeric(string='Current Rate',
readonly=True, digits=(16, Eval('currency_digits', 4)), readonly=True, digits=(16, Eval('currency_digits', 4)),
depends=['currency_digits']), 'get_rate_data') depends=['currency_digits']),
'get_rate_data', searcher='search_rate')
date = fields.Function(fields.Date(string='Date', readonly=True, date = fields.Function(fields.Date(string='Date', readonly=True,
help='Date of current rate'), 'get_rate_data') help='Date of current rate'),
'get_rate_data', searcher='search_date')
company_currency = fields.Function(fields.Many2One(readonly=True, company_currency = fields.Function(fields.Many2One(readonly=True,
string='Company Currency', states={'invisible': True}, string='Company Currency', states={'invisible': True},
@ -74,6 +81,8 @@ class Asset(ModelSQL, ModelView):
updtsource = fields.Many2One(string='Update Source', updtsource = fields.Many2One(string='Update Source',
help='Select a source for the course update.', help='Select a source for the course update.',
ondelete='SET NULL', model_name='investment.source') ondelete='SET NULL', model_name='investment.source')
updtdays = fields.Selection(string='Select days', required=True,
selection=sel_updtdays)
updttime = fields.Time(string='Time', updttime = fields.Time(string='Time',
states={ states={
'readonly': ~Bool(Eval('updtsource')), 'readonly': ~Bool(Eval('updtsource')),
@ -114,7 +123,7 @@ class Asset(ModelSQL, ModelView):
def view_attributes(cls): def view_attributes(cls):
return super().view_attributes() + [ return super().view_attributes() + [
('/tree', 'visual', ('/tree', 'visual',
If(Eval('date') < Date(delta_days=-5), 'muted', If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
If(Eval('change_day1', 0) < 0, 'warning', If(Eval('change_day1', 0) < 0, 'warning',
If(Eval('change_day1', 0) > 0, 'success', '') If(Eval('change_day1', 0) > 0, 'success', '')
)) ))
@ -150,43 +159,10 @@ class Asset(ModelSQL, ModelView):
return time(14, 0) return time(14, 0)
@classmethod @classmethod
def get_rate_data(cls, assets, names): def default_updtdays(cls):
""" get date and rate of asset """ default: mon - fri
""" """
pool = Pool() return 'work'
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
cursor = Transaction().connection.cursor()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset
).select(
tab_asset.id,
tab_rate.rate,
tab_rate.date,
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc],
where=tab_asset.id.in_([x.id for x in assets]),
)
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, rate1, date1) = record
asset = Asset(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
values = {'rate': record[1].quantize(exp), 'date': record[2]}
for name in names:
result[name][record[0]] = values[name]
return result
@fields.depends('updtsource', 'updttime') @fields.depends('updtsource', 'updttime')
def on_change_updtsource(self): def on_change_updtsource(self):
@ -246,6 +222,106 @@ class Asset(ModelSQL, ModelView):
if self.company.currency.id != self.currency.id: if self.company.currency.id != self.currency.id:
return self.company.currency.id return self.company.currency.id
@classmethod
def get_rate_data_sql(cls):
""" get sql for rate/date
"""
pool = Pool()
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset
).select(
tab_asset.id,
Round(tab_rate.rate, tab_asset.currency_digits).as_('rate'),
tab_rate.date,
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc],
)
return (query, tab_asset)
@classmethod
def get_rate_data(cls, assets, names):
""" get date and rate of asset
"""
cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_rate_data_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, rate1, date1) = record
asset = Asset(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
values = {'rate': record[1].quantize(exp), 'date': record[2]}
for name in names:
result[name][record[0]] = values[name]
return result
@classmethod
def search_date(cls, names, clause):
""" search in date
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.date, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def search_rate(cls, names, clause):
""" search in rate
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.rate, clause[2]),
)
return [('id', 'in', query)]
@staticmethod
def order_date(tables):
""" order date
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.date,
where=tab_query.id==table.id,
)
return [query]
@staticmethod
def order_rate(tables):
""" order rate
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.rate,
where=tab_query.id==table.id,
)
return [query]
@classmethod @classmethod
def get_percentage_sql(cls, name_lst, select_date=True): def get_percentage_sql(cls, name_lst, select_date=True):
""" get table for percentages and dates, """ get table for percentages and dates,
@ -331,7 +407,7 @@ class Asset(ModelSQL, ModelView):
table, _ = tables[None] table, _ = tables[None]
query = tab_asset.select( query = tab_asset.select(
getattr(tab_asset, 'day1'), tab_asset.day1,
where=tab_asset.id==table.id, where=tab_asset.id==table.id,
) )
return [query] return [query]
@ -345,7 +421,7 @@ class Asset(ModelSQL, ModelView):
table, _ = tables[None] table, _ = tables[None]
query = tab_asset.select( query = tab_asset.select(
getattr(tab_asset, 'month1'), tab_asset.month1,
where=tab_asset.id==table.id, where=tab_asset.id==table.id,
) )
return [query] return [query]
@ -359,7 +435,7 @@ class Asset(ModelSQL, ModelView):
table, _ = tables[None] table, _ = tables[None]
query = tab_asset.select( query = tab_asset.select(
getattr(tab_asset, 'month3'), tab_asset.month3,
where=tab_asset.id==table.id, where=tab_asset.id==table.id,
) )
return [query] return [query]
@ -373,7 +449,7 @@ class Asset(ModelSQL, ModelView):
table, _ = tables[None] table, _ = tables[None]
query = tab_asset.select( query = tab_asset.select(
getattr(tab_asset, 'month6'), tab_asset.month6,
where=tab_asset.id==table.id, where=tab_asset.id==table.id,
) )
return [query] return [query]
@ -387,7 +463,7 @@ class Asset(ModelSQL, ModelView):
table, _ = tables[None] table, _ = tables[None]
query = tab_asset.select( query = tab_asset.select(
getattr(tab_asset, 'month12'), tab_asset.month12,
where=tab_asset.id==table.id, where=tab_asset.id==table.id,
) )
return [query] return [query]
@ -443,16 +519,30 @@ class Asset(ModelSQL, ModelView):
context = Transaction().context context = Transaction().context
query_date = context.get('qdate', CurrentDate() - Literal(1)) query_date = context.get('qdate', CurrentDate() - Literal(1))
query = tab_asset.join(tab_rate,
# get last date of rate
tab_date = tab_asset.join(tab_rate,
condition=tab_asset.id == tab_rate.asset, condition=tab_asset.id == tab_rate.asset,
type_ = 'LEFT OUTER', type_ = 'LEFT OUTER',
).select( ).select(
tab_asset.id, tab_asset.id,
((Coalesce(tab_rate.date, query_date) + Literal(1)) + \ (Coalesce(tab_rate.date, query_date) + Literal(1)).as_('date'),
tab_asset.updttime).as_('updttime'), tab_asset.updtdays,
tab_asset.updttime,
distinct_on = [tab_asset.id], distinct_on = [tab_asset.id],
order_by = [tab_asset.id, tab_rate.date.desc], order_by = [tab_asset.id, tab_rate.date.desc],
where=(tab_asset.updtsource != None), where=tab_asset.updtsource != None,
)
query = tab_date.select(
tab_date.id,
(Case(
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 0), tab_date.date + Literal(1)),
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 6), tab_date.date + Literal(2)),
else_ = tab_date.date,
) + tab_date.updttime).as_('updttime'),
) )
return query return query

View file

@ -89,6 +89,43 @@ class GraphDef(metaclass=PoolMeta):
class ChartPoint(metaclass=PoolMeta): class ChartPoint(metaclass=PoolMeta):
__name__ = 'diagram.point' __name__ = 'diagram.point'
@classmethod
def get_interpolated_val(cls, keyname, query_date):
""" query two neighbour-values to
interpolate missing value
"""
Rate = Pool().get('investment.rate')
if keyname is None:
return None
# check if query is for us
if keyname.startswith('asset'):
asset_id = int(keyname[len('asset'):])
before = Rate.search([
('date', '<', query_date),
('asset.id', '=', asset_id),
], limit=1, order=[('date', 'DESC')])
after = Rate.search([
('date', '>', query_date),
('asset.id', '=', asset_id),
], limit=1, order=[('date', 'ASC')])
if (len(before) == 1) and (len(after) == 1):
result = cls.interpolate_linear(
(after[0].date, after[0].rate),
(before[0].date, before[0].rate),
query_date
)
return result
elif len(before) == 1:
return before[0].rate
elif len(after) == 1:
return after[0].rate
return super(ChartPoint, cls).get_interpolated_val(keyname, query_date)
@classmethod @classmethod
def get_table_parts(cls): def get_table_parts(cls):
""" return a list of tables to union, """ return a list of tables to union,

View file

@ -238,6 +238,18 @@ msgctxt "help:investment.asset,change_month12:"
msgid "percentage change in value during 1 year" msgid "percentage change in value during 1 year"
msgstr "Prozentuale Wertänderung während 1 Jahr" msgstr "Prozentuale Wertänderung während 1 Jahr"
msgctxt "field:investment.asset,updtdays:"
msgid "Select days"
msgstr "Tage wählen"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Fri"
msgstr "Mo - Fr"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mo - So"
##################### #####################
# investment.source # # investment.source #

View file

@ -210,6 +210,18 @@ msgctxt "help:investment.asset,change_month12:"
msgid "percentage change in value during 1 year" msgid "percentage change in value during 1 year"
msgstr "percentage change in value during 1 year" msgstr "percentage change in value during 1 year"
msgctxt "field:investment.asset,updtdays:"
msgid "Select days"
msgstr "Select days"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Fri"
msgstr "Mon - Fri"
msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mon - Sun"
msgctxt "model:investment.source,name:" msgctxt "model:investment.source,name:"
msgid "Online Source" msgid "Online Source"
msgstr "Online Source" msgstr "Online Source"

View file

@ -112,6 +112,82 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(asset.rec_name, 'Product 1 - 2.4500 usd/Unit [05/15/2022]') self.assertEqual(asset.rec_name, 'Product 1 - 2.4500 usd/Unit [05/15/2022]')
self.assertEqual(Asset.search_count([('name', '=', 'Product 1')]), 1) self.assertEqual(Asset.search_count([('name', '=', 'Product 1')]), 1)
@with_transaction()
def test_asset_order_and_search_rate_and_date(self):
""" create asset, check order of rate + date
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product1 = self.prep_asset_product(
name='Product 1',
description='some asset')
product2 = self.prep_asset_product(
name='Product 2',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product1)
asset2 = self.prep_asset_item(
company=company,
product = product2)
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 18),
'rate': Decimal('3.5'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 17),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 14),
'rate': Decimal('2.4'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 - 3.5000 usd/Unit [05/18/2022]')
self.assertEqual(asset2.rec_name, 'Product 2 - 2.6000 usd/Unit [05/17/2022]')
assets = Asset.search([], order=[('date', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 17))
self.assertEqual(assets[1].date, date(2022, 5, 18))
assets = Asset.search([], order=[('date', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 18))
self.assertEqual(assets[1].date, date(2022, 5, 17))
assets = Asset.search([], order=[('rate', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('2.6'))
self.assertEqual(assets[1].rate, Decimal('3.5'))
assets = Asset.search([], order=[('rate', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('3.5'))
self.assertEqual(assets[1].rate, Decimal('2.6'))
self.assertEqual(Asset.search_count([
('date', '=', date(2022, 5, 17)),
]), 1)
self.assertEqual(Asset.search_count([
('date', '>=', date(2022, 5, 17)),
]), 2)
self.assertEqual(Asset.search_count([
('date', '<', date(2022, 5, 17)),
]), 0)
@with_transaction() @with_transaction()
def test_asset_percentages_dateselect1(self): def test_asset_percentages_dateselect1(self):
""" create asset, add rates, check selection of """ create asset, add rates, check selection of
@ -535,7 +611,7 @@ class AssetTestCase(ModuleTestCase):
}]) }])
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 10, 14), 'qdate': date(2022, 10, 14), # friday
}): }):
# re-read to make context work # re-read to make context work
asset2, = Asset.browse([asset.id]) asset2, = Asset.browse([asset.id])
@ -543,21 +619,24 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(asset2.updtsource.rec_name, 'Source 1') self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
self.assertEqual(asset2.updttime, time(10, 45)) self.assertEqual(asset2.updttime, time(10, 45))
self.assertEqual(len(asset2.rates), 0) self.assertEqual(len(asset2.rates), 0)
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 15, 10, 45)) # qdate = 2022-10-14 simulates existence of record at this day
# next call would be the 15. - but its saturday,
# next-call-date is moved to 17.
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 17, 10, 45))
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '<', datetime(2022, 10, 17, 10, 45))]),
0) 0)
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]),
1) 1)
# add rate at yesterday # add rate at next monday
Asset.write(*[ Asset.write(*[
[asset], [asset],
{ {
'rates': [('create', [{ 'rates': [('create', [{
'date': date(2022, 10, 14), 'date': date(2022, 10, 17), # monday
'rate': Decimal('1.5'), 'rate': Decimal('1.5'),
}])], }])],
}]) }])
@ -567,14 +646,14 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(asset.updtsource.rec_name, 'Source 1') self.assertEqual(asset.updtsource.rec_name, 'Source 1')
self.assertEqual(asset.updttime, time(10, 45)) self.assertEqual(asset.updttime, time(10, 45))
self.assertEqual(len(asset.rates), 1) self.assertEqual(len(asset.rates), 1)
self.assertEqual(asset.rates[0].date, date(2022, 10, 14)) self.assertEqual(asset.rates[0].date, date(2022, 10, 17))
self.assertEqual(asset.nextupdate, datetime(2022, 10, 15, 10, 45)) self.assertEqual(asset.nextupdate, datetime(2022, 10, 18, 10, 45))
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '<', datetime(2022, 10, 18, 10, 45))]),
0) 0)
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 18, 10, 45))]),
1) 1)
# add rate at today # add rate at today
@ -582,7 +661,7 @@ class AssetTestCase(ModuleTestCase):
[asset], [asset],
{ {
'rates': [('create', [{ 'rates': [('create', [{
'date': date(2022, 10, 15), 'date': date(2022, 10, 18),
'rate': Decimal('1.5'), 'rate': Decimal('1.5'),
}])], }])],
}]) }])
@ -592,14 +671,14 @@ class AssetTestCase(ModuleTestCase):
self.assertEqual(asset2.updtsource.rec_name, 'Source 1') self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
self.assertEqual(asset2.updttime, time(10, 45)) self.assertEqual(asset2.updttime, time(10, 45))
self.assertEqual(len(asset2.rates), 2) self.assertEqual(len(asset2.rates), 2)
self.assertEqual(asset2.rates[0].date, date(2022, 10, 15)) self.assertEqual(asset2.rates[0].date, date(2022, 10, 18))
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 16, 10, 45)) self.assertEqual(asset2.nextupdate, datetime(2022, 10, 19, 10, 45))
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '<', datetime(2022, 10, 19, 10, 45))]),
0) 0)
self.assertEqual( self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 19, 10, 45))]),
1) 1)
@with_transaction() @with_transaction()

View file

@ -68,7 +68,7 @@ class SourceTestCase(ModuleTestCase):
}]) }])
with Transaction().set_context({ with Transaction().set_context({
'qdate': date(2022, 10, 1), 'qdate': date(2022, 10, 1), # saturday
'qdatetime': datetime(2022, 10, 2, 10, 0, 0), 'qdatetime': datetime(2022, 10, 2, 10, 0, 0),
}): }):
asset2, = Asset.browse([asset]) asset2, = Asset.browse([asset])
@ -77,9 +77,11 @@ class SourceTestCase(ModuleTestCase):
self.assertEqual(asset2.secsymb, '1472977') self.assertEqual(asset2.secsymb, '1472977')
self.assertEqual(asset2.updttime, time(14, 0)) self.assertEqual(asset2.updttime, time(14, 0))
self.assertEqual(asset2.updtsource.rec_name, 'Source 1') self.assertEqual(asset2.updtsource.rec_name, 'Source 1')
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 2, 14, 0)) self.assertEqual(asset2.updtdays, 'work')
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 3, 14, 0))
self.assertEqual(len(asset.rates), 0) self.assertEqual(len(asset.rates), 0)
# fake server-response # fake server-response
resp1 = Response() resp1 = Response()
resp1._content = """<html><body>Response from finance-server resp1._content = """<html><body>Response from finance-server

View file

@ -61,6 +61,10 @@ full copyright notices and license terms. -->
<field name="updtsource"/> <field name="updtsource"/>
<label name="updttime"/> <label name="updttime"/>
<field name="updttime"/> <field name="updttime"/>
<label id="labupdt" colspan="2" string=" "/>
<label name="updtdays"/>
<field name="updtdays"/>
</page> </page>
</notebook> </notebook>