From 7334e53f9f8594badebf248afc03570adb103d68 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 28 Nov 2022 23:15:28 +0100 Subject: [PATCH] =?UTF-8?q?diagram:=20interpolator=20erg=C3=A4nzt,=20asset?= =?UTF-8?q?:=20berechnung=20'nextupdate'=20korrigiert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- asset.py | 190 +++++++++++++++++++++++++++++++------------ diagram.py | 37 +++++++++ locale/de.po | 12 +++ locale/en.po | 12 +++ tests/test_asset.py | 109 +++++++++++++++++++++---- tests/test_source.py | 6 +- view/asset_form.xml | 4 + 7 files changed, 303 insertions(+), 67 deletions(-) diff --git a/asset.py b/asset.py index 62d9ef0..c4ffab5 100644 --- a/asset.py +++ b/asset.py @@ -6,17 +6,22 @@ from trytond.model import ModelView, ModelSQL, fields from trytond.transaction import Transaction 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 decimal import Decimal 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 import Literal digits_percent = 2 +sel_updtdays = [ + ('work', 'Mon - Fri'), + ('week', 'Mon - Sun'), + ] + class Asset(ModelSQL, ModelView): 'Asset' @@ -45,9 +50,11 @@ class Asset(ModelSQL, ModelView): model_name='investment.rate') rate = fields.Function(fields.Numeric(string='Current Rate', 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, - 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, string='Company Currency', states={'invisible': True}, @@ -74,6 +81,8 @@ class Asset(ModelSQL, ModelView): updtsource = fields.Many2One(string='Update Source', help='Select a source for the course update.', ondelete='SET NULL', model_name='investment.source') + updtdays = fields.Selection(string='Select days', required=True, + selection=sel_updtdays) updttime = fields.Time(string='Time', states={ 'readonly': ~Bool(Eval('updtsource')), @@ -114,7 +123,7 @@ class Asset(ModelSQL, ModelView): def view_attributes(cls): return super().view_attributes() + [ ('/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, 'success', '') )) @@ -150,43 +159,10 @@ class Asset(ModelSQL, ModelView): return time(14, 0) @classmethod - def get_rate_data(cls, assets, names): - """ get date and rate of asset + def default_updtdays(cls): + """ default: mon - fri """ - pool = Pool() - 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 + return 'work' @fields.depends('updtsource', 'updttime') def on_change_updtsource(self): @@ -246,6 +222,106 @@ class Asset(ModelSQL, ModelView): if self.company.currency.id != self.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 def get_percentage_sql(cls, name_lst, select_date=True): """ get table for percentages and dates, @@ -331,7 +407,7 @@ class Asset(ModelSQL, ModelView): table, _ = tables[None] query = tab_asset.select( - getattr(tab_asset, 'day1'), + tab_asset.day1, where=tab_asset.id==table.id, ) return [query] @@ -345,7 +421,7 @@ class Asset(ModelSQL, ModelView): table, _ = tables[None] query = tab_asset.select( - getattr(tab_asset, 'month1'), + tab_asset.month1, where=tab_asset.id==table.id, ) return [query] @@ -359,7 +435,7 @@ class Asset(ModelSQL, ModelView): table, _ = tables[None] query = tab_asset.select( - getattr(tab_asset, 'month3'), + tab_asset.month3, where=tab_asset.id==table.id, ) return [query] @@ -373,7 +449,7 @@ class Asset(ModelSQL, ModelView): table, _ = tables[None] query = tab_asset.select( - getattr(tab_asset, 'month6'), + tab_asset.month6, where=tab_asset.id==table.id, ) return [query] @@ -387,7 +463,7 @@ class Asset(ModelSQL, ModelView): table, _ = tables[None] query = tab_asset.select( - getattr(tab_asset, 'month12'), + tab_asset.month12, where=tab_asset.id==table.id, ) return [query] @@ -443,16 +519,30 @@ class Asset(ModelSQL, ModelView): context = Transaction().context 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, type_ = 'LEFT OUTER', ).select( tab_asset.id, - ((Coalesce(tab_rate.date, query_date) + Literal(1)) + \ - tab_asset.updttime).as_('updttime'), + (Coalesce(tab_rate.date, query_date) + Literal(1)).as_('date'), + tab_asset.updtdays, + tab_asset.updttime, distinct_on = [tab_asset.id], 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 diff --git a/diagram.py b/diagram.py index cfb2c37..a2fe05b 100644 --- a/diagram.py +++ b/diagram.py @@ -89,6 +89,43 @@ class GraphDef(metaclass=PoolMeta): class ChartPoint(metaclass=PoolMeta): __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 def get_table_parts(cls): """ return a list of tables to union, diff --git a/locale/de.po b/locale/de.po index 4498178..c100853 100644 --- a/locale/de.po +++ b/locale/de.po @@ -238,6 +238,18 @@ msgctxt "help:investment.asset,change_month12:" msgid "percentage change in value during 1 year" 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 # diff --git a/locale/en.po b/locale/en.po index 3b4a074..22530ee 100644 --- a/locale/en.po +++ b/locale/en.po @@ -210,6 +210,18 @@ msgctxt "help:investment.asset,change_month12:" msgid "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:" msgid "Online Source" msgstr "Online Source" diff --git a/tests/test_asset.py b/tests/test_asset.py index a6ad279..1d65593 100644 --- a/tests/test_asset.py +++ b/tests/test_asset.py @@ -112,6 +112,82 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(asset.rec_name, 'Product 1 - 2.4500 usd/Unit [05/15/2022]') 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() def test_asset_percentages_dateselect1(self): """ create asset, add rates, check selection of @@ -535,7 +611,7 @@ class AssetTestCase(ModuleTestCase): }]) with Transaction().set_context({ - 'qdate': date(2022, 10, 14), + 'qdate': date(2022, 10, 14), # friday }): # re-read to make context work asset2, = Asset.browse([asset.id]) @@ -543,21 +619,24 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(asset2.updtsource.rec_name, 'Source 1') self.assertEqual(asset2.updttime, time(10, 45)) 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( - Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '<', datetime(2022, 10, 17, 10, 45))]), 0) self.assertEqual( - Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]), 1) - # add rate at yesterday + # add rate at next monday Asset.write(*[ [asset], { 'rates': [('create', [{ - 'date': date(2022, 10, 14), + 'date': date(2022, 10, 17), # monday 'rate': Decimal('1.5'), }])], }]) @@ -567,14 +646,14 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(asset.updtsource.rec_name, 'Source 1') self.assertEqual(asset.updttime, time(10, 45)) self.assertEqual(len(asset.rates), 1) - self.assertEqual(asset.rates[0].date, date(2022, 10, 14)) - self.assertEqual(asset.nextupdate, datetime(2022, 10, 15, 10, 45)) + self.assertEqual(asset.rates[0].date, date(2022, 10, 17)) + self.assertEqual(asset.nextupdate, datetime(2022, 10, 18, 10, 45)) self.assertEqual( - Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '<', datetime(2022, 10, 18, 10, 45))]), 0) self.assertEqual( - Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 18, 10, 45))]), 1) # add rate at today @@ -582,7 +661,7 @@ class AssetTestCase(ModuleTestCase): [asset], { 'rates': [('create', [{ - 'date': date(2022, 10, 15), + 'date': date(2022, 10, 18), 'rate': Decimal('1.5'), }])], }]) @@ -592,14 +671,14 @@ class AssetTestCase(ModuleTestCase): self.assertEqual(asset2.updtsource.rec_name, 'Source 1') self.assertEqual(asset2.updttime, time(10, 45)) self.assertEqual(len(asset2.rates), 2) - self.assertEqual(asset2.rates[0].date, date(2022, 10, 15)) - self.assertEqual(asset2.nextupdate, datetime(2022, 10, 16, 10, 45)) + self.assertEqual(asset2.rates[0].date, date(2022, 10, 18)) + self.assertEqual(asset2.nextupdate, datetime(2022, 10, 19, 10, 45)) self.assertEqual( - Asset.search_count([('nextupdate', '<', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '<', datetime(2022, 10, 19, 10, 45))]), 0) self.assertEqual( - Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 15, 10, 45))]), + Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 19, 10, 45))]), 1) @with_transaction() diff --git a/tests/test_source.py b/tests/test_source.py index b0ca4e0..0ce1e92 100644 --- a/tests/test_source.py +++ b/tests/test_source.py @@ -68,7 +68,7 @@ class SourceTestCase(ModuleTestCase): }]) with Transaction().set_context({ - 'qdate': date(2022, 10, 1), + 'qdate': date(2022, 10, 1), # saturday 'qdatetime': datetime(2022, 10, 2, 10, 0, 0), }): asset2, = Asset.browse([asset]) @@ -77,9 +77,11 @@ class SourceTestCase(ModuleTestCase): self.assertEqual(asset2.secsymb, '1472977') self.assertEqual(asset2.updttime, time(14, 0)) 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) + # fake server-response resp1 = Response() resp1._content = """Response from finance-server diff --git a/view/asset_form.xml b/view/asset_form.xml index 6b0548d..ec517b7 100644 --- a/view/asset_form.xml +++ b/view/asset_form.xml @@ -61,6 +61,10 @@ full copyright notices and license terms. -->