Compare commits

..

64 commits

Author SHA1 Message Date
Frederik Jaeckel
4a3d996600 optimize code 2024-01-21 19:37:48 +01:00
Frederik Jaeckel
ab6ab32d0a update online source 'www.sbroker.de' 2024-01-21 19:14:05 +01:00
Frederik Jaeckel
4a2135512f accept http 410 on server call 2024-01-21 19:08:41 +01:00
Frederik Jaeckel
9f74b8fbf7 check regex-code when save online-source 2024-01-21 18:55:21 +01:00
Frederik Jaeckel
c2df388692 asset: columns optional 2023-12-03 18:20:22 +01:00
Frederik Jaeckel
18ef1aaeb6 Tryton 7.0 2023-12-01 13:31:43 +01:00
Frederik Jaeckel
faefea3b5f formatting 2023-12-01 13:29:46 +01:00
Frederik Jaeckel
aed948fb8a asset-list: remove percent-symbol to speed up list view 2023-06-23 21:40:55 +02:00
Frederik Jaeckel
ff5893b451 diagram: simplify queries 2023-06-23 16:41:39 +02:00
Frederik Jaeckel
6e77058946 asset: avoid exceptions in get_identifiers() 2023-06-23 16:29:10 +02:00
Frederik Jaeckel
38e2c34a53 asset/rate: speed up percent-queries 2023-06-23 16:02:31 +02:00
Frederik Jaeckel
3b9de6c0bb asset: fixed possible exceptions 2023-06-22 17:28:58 +02:00
Frederik Jaeckel
ddffa302c4 remove: graph_form.xml 2023-06-07 21:57:27 +02:00
Frederik Jaeckel
1c5b31d2f1 Tryton 6.8: tests, indexes, info 2023-06-07 21:52:09 +02:00
Frederik Jaeckel
765738d9ee test: add more context 2023-06-07 20:06:36 +02:00
Frederik Jaeckel
ebb6d71be3 test: add context to product-create 2023-06-07 19:24:43 +02:00
Frederik Jaeckel
8f12741d76 formatting 2023-06-07 18:44:53 +02:00
Frederik Jaeckel
82f9d3a792 asset/onlinesource: add fixed url,
pre defned online sources: add FAZ.net
2023-04-21 16:55:51 +02:00
Frederik Jaeckel
edfa0424e5 online source: formatting 2023-04-21 15:40:07 +02:00
Frederik Jaeckel
26192ea831 add online-source www.finanzen.net - Aktie 2023-04-21 15:31:10 +02:00
Frederik Jaeckel
fbbb7bd96c setup.py 2023-02-28 09:13:29 +01:00
Frederik Jaeckel
6f1b3707bd readme updated 2023-02-28 09:05:52 +01:00
Frederik Jaeckel
23665ae6dc removed unused imports 2023-01-13 13:48:50 +01:00
Frederik Jaeckel
6789e63872 OnlineSource: Query method prepared for external extension 2023-01-13 13:08:52 +01:00
Frederik Jaeckel
40ef3ef192 remove caching, optimize rate/query 2023-01-10 19:54:32 +01:00
Frederik Jaeckel
0daf39b913 asset: cachezeit verkürzt 2023-01-09 22:21:03 +01:00
Frederik Jaeckel
8503fd3e32 caching für change_day1/month1/... 2023-01-08 20:56:27 +01:00
Frederik Jaeckel
8cfa54f693 asset: abfragezeit für prozente optimiert 2023-01-07 16:26:22 +01:00
Frederik Jaeckel
3dc79fc292 rate: optimize for speed 2023-01-05 22:25:16 +01:00
Frederik Jaeckel
8a4c8fa58f asset: Feld 'change_symbol' für Prozenzzeichen + test 2023-01-04 20:20:27 +01:00
Frederik Jaeckel
166a9e13a9 asset: spalte 'einheit' entfällt, Spalte 'Kurs' hat nun die Einheit 2023-01-04 17:12:46 +01:00
Frederik Jaeckel
d9403e4946 quellen korrigiert 2022-12-24 12:38:10 +01:00
Frederik Jaeckel
935be70112 setup - read_file 2022-12-20 12:31:36 +01:00
Frederik Jaeckel
347f09b49e test für wizard 2022-12-19 21:53:00 +01:00
Frederik Jaeckel
77d6e5f8ef import wizard 2022-12-19 21:13:32 +01:00
Frederik Jaeckel
94687139b1 onlinesource: sortierung, neues datumsformat 'dd.mm.yy',
neue quellen: finanzen.net/Stuttgard, s-broker
2022-12-18 13:40:13 +01:00
Frederik Jaeckel
94169fb416 asset: rekursion behoben, erlaubt aktion nach update 2022-12-16 14:17:28 +01:00
Frederik Jaeckel
ac0bced963 updates an asset nach online-aktion 2022-12-16 13:37:36 +01:00
Frederik Jaeckel
fcabafa080 asset: Feld 'symbol' in übersetzter form 2022-12-09 09:43:30 +01:00
Frederik Jaeckel
e30953a55c asset: prozentwerte mit einheit im form 2022-12-06 08:53:40 +01:00
Frederik Jaeckel
407569a684 asset: currency_digits limit --> 6 2022-12-05 22:34:51 +01:00
Frederik Jaeckel
9061039ec2 asset: bereichsgrenzen für currency_digits, farbe rot für tagesnegativ 2022-12-05 21:02:17 +01:00
Frederik Jaeckel
69afec62b0 import-script: add field-delimiter 2022-12-05 09:49:59 +01:00
Frederik Jaeckel
eb4af774c0 symbol fix 2022-12-03 00:08:55 +01:00
Frederik Jaeckel
e972d54f54 felder symbol, name, product_uom optimiert,
felder company_currency entfernt
2022-12-02 23:29:01 +01:00
Frederik Jaeckel
23d4790e3b icon 2022-12-02 14:58:54 +01:00
Frederik Jaeckel
71b52d106a asset: liste mit reiter für aktuell/inaktiv/alle 2022-12-02 12:56:14 +01:00
Frederik Jaeckel
f4310c596a asset: rec_name - einheit als währung/menge 2022-12-01 16:55:27 +01:00
Frederik Jaeckel
de99f0e473 asset: einheit als währung/menge 2022-12-01 16:30:50 +01:00
Frederik Jaeckel
c8c2afb190 icons 2022-11-30 21:50:31 +01:00
Frederik Jaeckel
78fc16d7e2 fix imports 2022-11-30 13:59:20 +01:00
Frederik Jaeckel
cc5a2fd164 setup.py 2022-11-30 13:35:21 +01:00
Frederik Jaeckel
9c5af080f5 importscript für historische kurse 2022-11-30 13:32:16 +01:00
Frederik Jaeckel
258c14e2b5 asset: online-quelle als liste 2022-11-29 21:54:27 +01:00
Frederik Jaeckel
7334e53f9f diagram: interpolator ergänzt,
asset: berechnung 'nextupdate' korrigiert
2022-11-28 23:15:28 +01:00
Frederik Jaeckel
d08cf19b60 asset: sortierung - neueste nach oben, farben - älter als 5 tage = gedimmt 2022-11-28 15:44:07 +01:00
Frederik Jaeckel
1a61b112e0 asset:tabellenzugriff optimiert,
diagram: darstellung in diagramm ergänzt
2022-11-26 22:42:02 +01:00
Frederik Jaeckel
83acfbb14b asset: suche in name, sortiert nach name, sortierer für wkn, isin, symbl 2022-11-25 22:46:32 +01:00
Frederik Jaeckel
82c451377c asset: spalten tag/monat/3monate... ok + test 2022-11-25 21:55:43 +01:00
Frederik Jaeckel
d70e674e58 abfrage der prozentwerte für tag/1 monat, 3 monate, 6 monate, 12 monate
test muß noch
2022-11-25 15:28:49 +01:00
Frederik Jaeckel
cba2117d13 asset: abfrage updatezeitpunkt optimiert, anzeige der vortagsprozente, farbe für zeilen 2022-11-25 11:00:03 +01:00
Frederik Jaeckel
34faf91476 prozentwerte begonnen 2022-11-24 23:17:44 +01:00
Frederik Jaeckel
f323334800 bug in updtneeded-suche, online-quellen erweitert 2022-11-23 22:22:22 +01:00
Frederik Jaeckel
1daa743631 asset: feld 'Datum' + 'Name' neu, rec_name optimiert, Test korrigiert 2022-11-23 10:44:09 +01:00
30 changed files with 1797 additions and 1606 deletions

View file

@ -9,111 +9,19 @@ pip install mds-investment
Requires
========
- Tryton 6.0
- Tryton 7.0
How to
======
Store values such as stocks, funds and commodities in Tryton.
Watch the performance. The Tryton module periodically loads
quotes of values from one or more price sources.
You can define the course sources yourself.
Changes
=======
*6.0.23 - 21.01.2023*
*7.0.0 - 01.12.2023*
- updt: online-source optimized for external extension
*6.0.22 - 10.01.2023*
- updt: optimze rate, remove caching
*6.0.21 - 08.01.2023*
- add: caching for percentual changes
*6.0.20 - 07.01.2023*
- updt: asset - percent-values optimize for speed
*6.0.19 - 05.01.2023*
- updt: rate - optimize for speed
*6.0.18 - 04.01.2023*
- add: units to lists
- updt: online-sources
*6.0.17 - 19.12.2022*
- add: import wizard
*6.0.16 - 18.12.2022*
- add: onlinesource - sorting, sources
*6.0.15 - 16.12.2022*
- fix: recoursion
- add: enable after-update actions
*6.0.14 - 09.12.2022*
- updt: translated symbol of asset-currency
*6.0.13 - 06.12.2022*
- updt: asset-form - units for percent-values, asset-list - day-negative in red-color
*6.0.12 - 05.12.2022*
- import-script - add field-delimter to param-list
*6.0.11 - 03.12.2022*
- add: tabs for asset-list, new icons, queries optimized
*6.0.10 - 01.12.2022*
- updt: optimized asset-list, unit as symbol
*6.0.9 - 30.11.2022*
- add: script to import historical rates
*6.0.8 - 29.11.2022*
- add: online-source as list to allow multiple sources
for asset
*6.0.7 - 28.11.2022*
- fix: add diagram-interpolate
- fix: corrected nextupdate
*6.0.6 - 26.11.2022*
- add: diagram-display
*6.0.5 - 25.11.2022*
- add: sorter for name/nsin/isin/symbol
- add: columns percentage by day/month/3month/6month/12month
- fix: online-updater
*6.0.4 - 25.11.2022*
- add: assets - colors for percentual success/loss
- updt: optimize timestamp for next online-update
*6.0.3 - 23.11.2022*
- fix: bug in searcher
- add: online-sources
*6.0.2 - 23.11.2022*
- asset: add field 'date', optimized 'rec_name'
*6.0.1 - 22.11.2022*
- works
*6.0.0 - 09.11.2022*
- init
- compatibility to Tryton 7.0

View file

@ -32,4 +32,3 @@ def register():
UpdateSoureWizard,
ImportWizard,
module='investment', type_='wizard')

450
asset.py
View file

@ -3,7 +3,7 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin, Index
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval, Bool, If, Date
@ -15,6 +15,7 @@ from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
from sql.conditionals import Case, Coalesce, NullIf
from sql import Literal
from .diagram import Concat2
from .const import DEF_NONE
digits_percent = 2
@ -29,94 +30,116 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'Asset'
__name__ = 'investment.asset'
name = fields.Function(fields.Char(string='Name', readonly=True),
name = fields.Function(fields.Char(
string='Name', readonly=True),
'get_name_symbol', searcher='search_rec_name')
company = fields.Many2One(string='Company', model_name='company.company',
company = fields.Many2One(
string='Company', model_name='company.company',
required=True, ondelete="RESTRICT")
product = fields.Many2One(string='Product', required=True,
model_name='product.product', ondelete='RESTRICT',
domain=[('type', '=', 'assets')])
product_uom = fields.Function(fields.Many2One(string='UOM Category',
readonly=True, model_name='product.uom.category',
product = fields.Many2One(
string='Product', required=True, model_name='product.product',
ondelete='RESTRICT', domain=[('type', '=', 'assets')])
product_uom = fields.Function(fields.Many2One(
string='UOM Category', readonly=True,
model_name='product.uom.category',
help='Category of unit on the product.'), 'get_name_symbol')
uom = fields.Many2One(string='UOM', required=True,
model_name='product.uom', ondelete='RESTRICT',
states={
'readonly': ~Bool(Eval('product')),
},
domain=[
('category', '=', Eval('product_uom')),
], depends=['product_uom', 'product'])
symbol = fields.Function(fields.Char(string='UOM', readonly=True),
'get_name_symbol', searcher='search_uom_symbol')
asset_symbol = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='investment.asset'),
uom = fields.Many2One(
string='UOM', required=True, model_name='product.uom',
ondelete='RESTRICT',
states={'readonly': ~Bool(Eval('product'))},
domain=[('category', '=', Eval('product_uom'))],
depends=['product_uom', 'product'])
symbol = fields.Function(fields.Char(
string='UOM', readonly=True), 'get_name_symbol',
searcher='search_uom_symbol')
asset_symbol = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='investment.asset'),
'get_name_symbol')
rates = fields.One2Many(string='Rates', field='asset',
model_name='investment.rate')
rate = fields.Function(fields.Numeric(string='Current Rate',
readonly=True, digits=(16, Eval('currency_digits', 4)),
depends=['currency_digits']),
rates = fields.One2Many(
string='Rates', field='asset', 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', searcher='search_rate')
date = fields.Function(fields.Date(string='Date', readonly=True,
help='Date of current rate'),
date = fields.Function(fields.Date(
string='Date', readonly=True, help='Date of current rate'),
'get_rate_data', searcher='search_date')
currency = fields.Many2One(string='Currency', select=True,
required=True, model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer(string='Digits', required=True,
domain=[
('currency_digits', '>=', 0),
('currency_digits', '<=', 6)])
currency = fields.Many2One(
string='Currency', required=True,
model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer(
string='Digits', required=True,
domain=[('currency_digits', '>=', 0), ('currency_digits', '<=', 6)])
wkn = fields.Function(fields.Char(string='NSIN', readonly=True,
wkn = fields.Function(fields.Char(
string='NSIN', readonly=True,
help='National Securities Identifying Number'),
'get_identifiers', searcher='search_identifier')
isin = fields.Function(fields.Char(string='ISIN', readonly=True,
isin = fields.Function(fields.Char(
string='ISIN', readonly=True,
help='International Securities Identification Number'),
'get_identifiers', searcher='search_identifier')
secsymb = fields.Function(fields.Char(string='Symbol', readonly=True,
secsymb = fields.Function(fields.Char(
string='Symbol', readonly=True,
help='Stock market symbol'),
'get_identifiers', searcher='search_identifier')
updtsources = fields.Many2Many(string='Update Sources',
help='Select sources for the course update. The course sources are tried until a valid value has been read.',
updtsources = fields.Many2Many(
string='Update Sources',
help='Select sources for the course update. The course sources ' +
'are tried until a valid value has been read.',
relation_name='investment.asset_source_rel',
origin='asset', target='source')
updtdays = fields.Selection(string='Select days', required=True,
selection=sel_updtdays)
updttime = fields.Time(string='Time',
updturl = fields.Char(
string='URL',
help='URL for data retrieval.',
states={
'readonly': ~Bool(Eval('updtsources')),
}, depends=['updtsources'])
nextupdate = fields.Function(fields.DateTime(string='Next Update',
readonly=True),
'invisible': ~Eval('updturl_enable', False),
'required': Eval('updturl_enable', False),
}, depends=['updturl_enable'])
updturl_enable = fields.Function(fields.Boolean(
string='URL required', readonly=True,
states={'invisible': True}),
'on_change_with_updturl_enable')
updtdays = fields.Selection(
string='Select days', required=True, selection=sel_updtdays)
updttime = fields.Time(
string='Time',
states={'readonly': ~Bool(Eval('updtsources'))},
depends=['updtsources'])
nextupdate = fields.Function(fields.DateTime(
string='Next Update', readonly=True),
'get_nextupdates', searcher='search_nextupdate')
# percentage change
change_day1 = fields.Function(fields.Numeric(string='Previous Day',
help='percentage change in value compared to the previous day',
readonly=True, digits=(16,digits_percent)),
change_day1 = fields.Function(fields.Numeric(
string='Previous Day', readonly=True,
digits=(16, digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month1 = fields.Function(fields.Numeric(string='1 Month',
change_month1 = fields.Function(fields.Numeric(
string='1 Month', readonly=True,
help='percentage change in value compared to last month',
readonly=True, digits=(16,digits_percent)),
digits=(16, digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month3 = fields.Function(fields.Numeric(string='3 Months',
change_month3 = fields.Function(fields.Numeric(
string='3 Months',
help='percentage change in value during 3 months',
readonly=True, digits=(16,digits_percent)),
digits=(16, digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month6 = fields.Function(fields.Numeric(string='6 Months',
change_month6 = fields.Function(fields.Numeric(
string='6 Months', readonly=True,
help='percentage change in value during 6 months',
readonly=True, digits=(16,digits_percent)),
digits=(16, digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month12 = fields.Function(fields.Numeric(string='1 Year',
change_month12 = fields.Function(fields.Numeric(
string='1 Year', readonly=True,
help='percentage change in value during 1 year',
readonly=True, digits=(16,digits_percent)),
digits=(16, digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_symbol = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='investment.rate'),
change_symbol = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='investment.rate'),
'get_rate_data')
@classmethod
@ -131,6 +154,21 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
super(Asset, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('date', 'DESC'))
t = cls.__table__()
cls._sql_indexes.update({
Index(
t,
(t.product, Index.Equality())),
Index(
t,
(t.currency, Index.Equality())),
Index(
t,
(t.uom, Index.Equality())),
Index(
t,
(t.updtdays, Index.Equality())),
})
@classmethod
def migrate_updtsource(cls, module_name):
@ -148,7 +186,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_asset.select(
tab_asset.id,
tab_asset.updtsource,
where = tab_asset.updtsource != None,
where=tab_asset.updtsource != DEF_NONE,
)
cursor.execute(*query)
records = cursor.fetchall()
@ -157,7 +195,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'source': x[1],
} for x in records]
if len(to_create) > 0:
if to_create:
AssetSourceRel.create(to_create)
asset_table.drop_column('updtsource')
@ -167,9 +205,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
('/tree', 'visual',
If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
If(Eval('change_day1', 0) < 0, 'danger',
If(Eval('change_day1', 0) > 0, 'success', '')
))
),
If(Eval('change_day1', 0) > 0, 'success', '')))),
]
@classmethod
@ -206,13 +242,23 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
"""
return 'work'
@fields.depends('updtsources')
def on_change_with_updturl_enable(self, name=None):
""" return True if a source has fixed-url
"""
if self.updtsources:
for usource in self.updtsources:
if usource.fixed_url is True:
return True
return False
@fields.depends('updtsources', 'updttime')
def on_change_updtsources(self):
""" clear time-fields
"""
if len(self.updtsources) == 0:
if not self.updtsources:
self.updttime = None
else :
else:
self.updttime = time(11, 30)
@fields.depends('product', 'uom')
@ -250,16 +296,21 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
(tab1, join1, col1) = Uom.symbol._get_translation_column(Uom, 'symbol')
tab_symb = join1.select(tab1.id, col1.as_('symbol'))
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id,
).join(tab_templ,
condition=tab_templ.id==tab_prod.template,
).join(tab_uom,
condition=tab_templ.default_uom==tab_uom.id,
).join(tab_cur,
condition=tab_asset.currency==tab_cur.id,
).join(tab_symb,
condition=tab_asset.uom==tab_symb.id,
query = tab_asset.join(
tab_prod,
condition=tab_asset.product == tab_prod.id,
).join(
tab_templ,
condition=tab_templ.id == tab_prod.template,
).join(
tab_uom,
condition=tab_templ.default_uom == tab_uom.id,
).join(
tab_cur,
condition=tab_asset.currency == tab_cur.id,
).join(
tab_symb,
condition=tab_asset.uom == tab_symb.id,
).select(
tab_asset.id,
tab_templ.name,
@ -274,12 +325,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
"""
cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_name_symbol_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
result = {x: {y.id: None for y in assets} for x in names}
(query, tab_asset) = cls.get_name_symbol_sql()
if assets:
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:
values = {
@ -299,8 +351,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
"""
return ['OR',
(('uom.rec_name',) + tuple(clause[1:])),
(('currency.rec_name',) + tuple(clause[1:])),
]
(('currency.rec_name',) + tuple(clause[1:]))]
@classmethod
def get_rate_data_sql(cls):
@ -312,8 +363,9 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset
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'),
@ -328,22 +380,23 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
def get_rate_data(cls, assets, names):
""" get date and rate of asset
"""
Asset2 = Pool().get('investment.asset')
cursor = Transaction().connection.cursor()
result = {x: {y.id: None for y in assets} for x in names}
if assets:
(query, tab_asset) = cls.get_rate_data_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
query.where = tab_asset.id.in_([x.id for x in assets])
curr_digits = {x.id: x.currency_digits 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, id_rate) = record
asset = Asset2(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
curr_dig = curr_digits.get(id1, 4)
exp = Decimal(Decimal(1) / 10 ** curr_dig)
values = {
'rate': record[1].quantize(exp),
@ -353,7 +406,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
for name in names:
result[name][record[0]] = values[name]
return result
@classmethod
@ -365,8 +417,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.date, clause[2]),
)
where=Operator(tab_query.date, clause[2]))
return [('id', 'in', query)]
@classmethod
@ -378,8 +429,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.rate, clause[2]),
)
where=Operator(tab_query.rate, clause[2]))
return [('id', 'in', query)]
@staticmethod
@ -391,8 +441,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select(
tab_query.date,
where=tab_query.id==table.id,
)
where=tab_query.id == table.id)
return [query]
@staticmethod
@ -404,8 +453,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_query.select(
tab_query.rate,
where=tab_query.id==table.id,
)
where=tab_query.id == table.id)
return [query]
@classmethod
@ -416,8 +464,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
"""
pool = Pool()
Rate = pool.get('investment.rate')
Asset2 = pool.get('investment.asset')
tab_asset = Asset2.__table__()
tab_rate1 = Rate.__table__()
tab_rate2 = Rate.__table__()
context = Transaction().context
@ -425,103 +471,96 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query_date = context.get('qdate', CurrentDate())
where_asset = tab_rate1.date <= query_date
if isinstance(asset_ids, list):
where_asset &= tab_asset.id.in_(asset_ids)
where_asset &= tab_rate1.asset.in_(asset_ids)
tab_today = tab_asset.join(tab_rate1,
condition=tab_asset.id==tab_rate1.asset,
).select(
tab_asset.id,
tab_today = tab_rate1.select(
tab_rate1.asset.as_('id'),
tab_rate1.date,
tab_rate1.rate,
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate1.date.desc],
where=where_asset,
)
distinct_on=[tab_rate1.asset],
order_by=[tab_rate1.asset, tab_rate1.date.desc],
where=where_asset)
days_diff = days + 5
query = tab_today.join(tab_rate2,
condition=(tab_today.id==tab_rate2.asset) & \
(tab_today.date > (tab_rate2.date + days)) & \
query = tab_today.join(
tab_rate2,
condition=(tab_today.id == tab_rate2.asset) &
(tab_today.date > (tab_rate2.date + days)) &
(tab_today.date <= (tab_rate2.date + days_diff)),
type_ = 'LEFT OUTER',
type_='LEFT OUTER',
).select(
tab_today.id,
tab_today.date,
tab_today.rate,
(tab_today.rate * 100.0 / NullIf(tab_rate2.rate, 0.00) - 100.0).as_('percent'),
(tab_today.rate * 100.0 / NullIf(tab_rate2.rate, 0.00) -
100.0).as_('percent'),
distinct_on=[tab_today.id],
order_by=[tab_today.id, tab_rate2.date.desc]
)
order_by=[tab_today.id, tab_rate2.date.desc])
return query
@staticmethod
def order_change_day1(tables):
""" order day1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=0)
Asset2 = Pool().get('investment.asset')
tab_asset = Asset2.get_percentage_sql(days=0)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
where=tab_asset.id == table.id)
return [query]
@staticmethod
def order_change_month1(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=30)
Asset2 = Pool().get('investment.asset')
tab_asset = Asset2.get_percentage_sql(days=30)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
where=tab_asset.id == table.id)
return [query]
@staticmethod
def order_change_month3(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=90)
Asset2 = Pool().get('investment.asset')
tab_asset = Asset2.get_percentage_sql(days=90)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
where=tab_asset.id == table.id)
return [query]
@staticmethod
def order_change_month6(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=180)
Asset2 = Pool().get('investment.asset')
tab_asset = Asset2.get_percentage_sql(days=180)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
where=tab_asset.id == table.id)
return [query]
@staticmethod
def order_change_month12(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=365)
Asset2 = Pool().get('investment.asset')
tab_asset = Asset2.get_percentage_sql(days=365)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
where=tab_asset.id == table.id)
return [query]
@classmethod
@ -538,9 +577,9 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'month12': 365,
}[field_name])
query = tab_percent.select(tab_percent.id,
where=Operator(Round(tab_percent.percent, 2),
clause[2]))
query = tab_percent.select(
tab_percent.id,
where=Operator(Round(tab_percent.percent, 2), clause[2]))
return [('id', 'in', query)]
@classmethod
@ -549,10 +588,11 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
"""
cursor = Transaction().connection.cursor()
result = {x:{y.id: None for y in assets} for x in names}
result = {x: {y.id: None for y in assets} for x in names}
exp = Decimal(Decimal(1) / 10 ** digits_percent)
asset_id_lst = [x.id for x in assets]
if asset_id_lst and names:
for x in names:
tab_percent = cls.get_percentage_sql(
days={
@ -562,8 +602,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
'change_month6': 180,
'change_month12': 365,
}[x],
asset_ids=asset_id_lst,
)
asset_ids=asset_id_lst)
cursor.execute(*tab_percent)
records = cursor.fetchall()
@ -588,32 +627,34 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query_date = context.get('qdate', CurrentDate() - Literal(1))
# get last date of rate
tab_date = tab_asset.join(tab_rel,
tab_date = tab_asset.join(
tab_rel,
# link to asset-source-relation to check if
# there are online-sources set
condition=tab_rel.asset == tab_asset.id,
).join(tab_rate,
).join(
tab_rate,
condition=tab_asset.id == tab_rate.asset,
type_ = 'LEFT OUTER',
type_='LEFT OUTER',
).select(
tab_asset.id,
(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],
)
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc])
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'),
)
((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
@classmethod
@ -627,12 +668,11 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_updt.select(
tab_updt.id,
tab_updt.updttime,
where=tab_updt.id.in_([x.id for x in assets]),
)
where=tab_updt.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}
result = {x: {y.id: None for y in assets} for x in names}
for record in records:
(id1, updt) = record
@ -652,8 +692,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_updt.select(
tab_updt.id,
where=Operator(tab_updt.updttime, clause[2]),
)
where=Operator(tab_updt.updttime, clause[2]))
return [('id', 'in', query)]
@classmethod
@ -668,26 +707,29 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_secsymb = Identifier.__table__()
tab_isin = Identifier.__table__()
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id,
).join(tab_wkn,
condition=(tab_prod.id==tab_wkn.product) & \
query = tab_asset.join(
tab_prod,
condition=tab_asset.product == tab_prod.id,
).join(
tab_wkn,
condition=(tab_prod.id == tab_wkn.product) &
(tab_wkn.type == 'wkn'),
type_ = 'LEFT OUTER',
).join(tab_secsymb,
condition=(tab_prod.id==tab_secsymb.product) & \
type_='LEFT OUTER',
).join(
tab_secsymb,
condition=(tab_prod.id == tab_secsymb.product) &
(tab_secsymb.type == 'secsymb'),
type_ = 'LEFT OUTER',
).join(tab_isin,
condition=(tab_prod.id==tab_isin.product) & \
type_='LEFT OUTER',
).join(
tab_isin,
condition=(tab_prod.id == tab_isin.product) &
(tab_isin.type == 'isin'),
type_ = 'LEFT OUTER',
type_='LEFT OUTER',
).select(
tab_asset.id,
tab_wkn.code.as_('wkn'),
tab_secsymb.code.as_('secsymb'),
tab_isin.code.as_('isin'),
)
tab_isin.code.as_('isin'))
return query
@staticmethod
@ -703,13 +745,15 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_prod = Product.__table__()
tab_templ = Templ.__table__()
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id
).join(tab_templ,
condition=tab_templ.id==tab_prod.template
).select(tab_templ.name,
where=tab_asset.id==table.id
)
query = tab_asset.join(
tab_prod,
condition=tab_asset.product == tab_prod.id
).join(
tab_templ,
condition=tab_templ.id == tab_prod.template
).select(
tab_templ.name,
where=tab_asset.id == table.id)
return [query]
@staticmethod
@ -722,8 +766,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select(
getattr(tab_ids, 'wkn'),
where=tab_ids.id==table.id,
)
where=tab_ids.id == table.id)
return [query]
@staticmethod
@ -736,8 +779,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select(
getattr(tab_ids, 'isin'),
where=tab_ids.id==table.id,
)
where=tab_ids.id == table.id)
return [query]
@staticmethod
@ -750,8 +792,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query = tab_ids.select(
getattr(tab_ids, 'secsymb'),
where=tab_ids.id==table.id,
)
where=tab_ids.id == table.id)
return [query]
@classmethod
@ -765,13 +806,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_ids = cls.get_identifier_sql(tab_asset)
field_qu = getattr(tab_ids, names)
query = tab_ids.join(tab_asset,
condition=tab_ids.id==tab_asset.id,
query = tab_ids.join(
tab_asset,
condition=tab_ids.id == tab_asset.id,
).select(
tab_asset.id,
where=Operator(field_qu, clause[2]) & \
(field_qu != None),
)
where=Operator(field_qu, clause[2]) &
(field_qu != DEF_NONE))
return [('id', 'in', query)]
@ -784,8 +825,8 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
tab_asset = Asset.__table__()
cursor = Transaction().connection.cursor()
result = {x:{y.id: None for y in assets} for x in names}
result = {x: {y.id: None for y in assets} for x in names}
if assets:
query = cls.get_identifier_sql(tab_asset)
query.where = tab_asset.id.in_([x.id for x in assets])
@ -798,7 +839,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
for n in names:
result[n][id1] = r1[n]
return result
def get_rec_name(self, name):
@ -807,17 +847,18 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
return '%(prod)s | %(rate)s %(unit)s | %(date)s' % {
'prod': getattr(self.product, 'rec_name', '-'),
'unit': self.symbol,
'rate': Report.format_number(self.rate, lang=None,
digits=self.currency_digits or 4) \
'rate': Report.format_number(
self.rate, lang=None, digits=self.currency_digits or 4)
if self.rate is not None else '-',
'date': Report.format_date(self.date) if self.date is not None else '-',
}
'date': Report.format_date(self.date)
if self.date is not None else '-'}
@classmethod
def search_rec_name(cls, name, clause):
""" search in rec_name
"""
return ['OR',
return [
'OR',
('product.rec_name',) + tuple(clause[1:]),
('product.identifiers.code',) + tuple(clause[1:]),
]
@ -840,12 +881,11 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
query_time = context.get('qdatetime', CurrentTimestamp())
to_run_activities = []
for asset in Asset2.search([
('nextupdate', '<=', query_time),
]):
('nextupdate', '<=', query_time)]):
if OnlineSource.update_rate(asset):
to_run_activities.append(asset)
if len(to_run_activities) > 0:
if to_run_activities:
cls.after_update_actions(to_run_activities)
# end Asset
@ -855,10 +895,12 @@ class AssetSourceRel(ModelSQL):
'Asset Source Relation'
__name__ = 'investment.asset_source_rel'
source = fields.Many2One(string='Online Source', select=True,
source = fields.Many2One(
string='Online Source',
required=True, model_name='investment.source',
ondelete='CASCADE')
asset = fields.Many2One(string='Asset', select=True,
asset = fields.Many2One(
string='Asset',
required=True, model_name='investment.asset',
ondelete='CASCADE')

8
const.py Normal file
View file

@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds.de for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
DEF_TRUE = True
DEF_NONE = None

View file

@ -22,8 +22,8 @@ class Concat2(Function):
class GraphDef(metaclass=PoolMeta):
__name__ = 'diagram.graphdef'
asset = fields.Many2One(string='Asset',
model_name='investment.asset',
asset = fields.Many2One(
string='Asset', model_name='investment.asset',
states={
'invisible': Eval('dtype', '') != 'investment.asset',
'required': Eval('dtype', '') == 'investment.asset',
@ -60,17 +60,18 @@ class GraphDef(metaclass=PoolMeta):
return None
if self.scaling == 'alldata':
query = [('asset.id', '=', self.asset.id)]
query = [('asset', '=', self.asset.id)]
elif self.scaling == 'view':
query = [
('asset.id', '=', self.asset.id),
('asset', '=', self.asset.id),
('date', '>=', self.chart.used_start_date()),
('date', '<=', self.chart.used_end_date()),
]
elif self.scaling == 'six':
query = [
('asset.id', '=', self.asset.id),
('date', '>=', self.chart.used_start_date() - timedelta(days=180)),
('asset', '=', self.asset.id),
('date', '>=', self.chart.used_start_date() -
timedelta(days=180)),
('date', '<=', self.chart.used_end_date()),
]
@ -94,7 +95,7 @@ class ChartPoint(metaclass=PoolMeta):
"""
Rate = Pool().get('investment.rate')
if keyname is None:
if not keyname:
return None
# check if query is for us
@ -103,12 +104,12 @@ class ChartPoint(metaclass=PoolMeta):
before = Rate.search([
('date', '<', query_date),
('asset.id', '=', asset_id),
('asset', '=', asset_id),
], limit=1, order=[('date', 'DESC')])
after = Rate.search([
('date', '>', query_date),
('asset.id', '=', asset_id),
('asset', '=', asset_id),
], limit=1, order=[('date', 'ASC')])
if (len(before) == 1) and (len(after) == 1):

View file

@ -36,15 +36,15 @@ class ImportWizardStart(ModelView):
'Import CSV-File'
__name__ = 'investment.imp_wiz.start'
asset = fields.Many2One(string='Asset', readonly=True,
model_name='investment.asset')
asset = fields.Many2One(
string='Asset', readonly=True, model_name='investment.asset')
file_ = fields.Binary(string="CSV-File", required=True)
dec_divider = fields.Selection(string='Decimal divider',
required=True, selection=sel_dec_divider)
date_fmt = fields.Selection(string='Date format',
required=True, selection=sel_date_fmt)
field_delimiter = fields.Selection(string='Field delimiter',
required=True, selection=sel_field_delimiter)
dec_divider = fields.Selection(
string='Decimal divider', required=True, selection=sel_dec_divider)
date_fmt = fields.Selection(
string='Date format', required=True, selection=sel_date_fmt)
field_delimiter = fields.Selection(
string='Field delimiter', required=True, selection=sel_field_delimiter)
# end ImportWizardStart
@ -54,11 +54,14 @@ class ImportWizard(Wizard):
__name__ = 'investment.imp_wiz'
start_state = 'start'
start = StateView(model_name='investment.imp_wiz.start', \
view='investment.imp_wiz_start_form', \
start = StateView(
model_name='investment.imp_wiz.start',
view='investment.imp_wiz_start_form',
buttons=[
Button(string='Cancel', state='end', icon='tryton-cancel'),
Button(string='Import File', state='importf', icon='tryton-import', default=True),
Button(
string='Import File', state='importf',
icon='tryton-import', default=True),
])
importf = StateTransition()
@ -81,14 +84,14 @@ class ImportWizard(Wizard):
pool = Pool()
ImportWiz = pool.get('investment.imp_wiz', type='wizard')
if self.start.file_ is not None:
if self.start.file_:
(lines, max_date, min_date) = ImportWiz.read_csv_file(
self.start.file_.decode('utf8'),
dec_divider = self.start.dec_divider,
date_fmt = self.start.date_fmt,
delimiter = self.start.field_delimiter)
dec_divider=self.start.dec_divider,
date_fmt=self.start.date_fmt,
delimiter=self.start.field_delimiter)
if len(lines) > 0:
if lines:
ImportWiz.upload_rates(
self.start.asset,
lines, min_date, max_date)
@ -140,8 +143,9 @@ class ImportWizard(Wizard):
max_rate = None
with StringIO(file_content) as fhdl:
csv_lines = csv.DictReader(fhdl,
fieldnames = ['date', 'rate'],
csv_lines = csv.DictReader(
fhdl,
fieldnames=['date', 'rate'],
dialect='excel',
delimiter=delimiter)
@ -150,58 +154,58 @@ class ImportWizard(Wizard):
if line.get('date', '') == 'date':
continue
try :
date_val = datetime.strptime(line.get('date', None).strip(), date_fmt).date()
except :
try:
date_val = datetime.strptime(
line.get('date', None).strip(), date_fmt).date()
except Exception:
raise UserError(gettext(
'investment.msg_import_err_date',
datefmt = date_fmt,
colnr = '1',
datefmt=date_fmt,
colnr='1',
))
try :
rate_val = line.get('rate', None).replace(del_chars[0], '').strip()
try:
rate_val = line.get('rate', None).replace(
del_chars[0], '').strip()
rate_val = Decimal(rate_val.replace(dec_divider, '.'))
except :
except Exception:
raise UserError(gettext(
'investment.msg_import_err_date',
datefmt = 'dd%sdd' % dec_divider,
colnr = '2',
))
datefmt='dd%sdd' % dec_divider,
colnr='2'))
if isinstance(date_val, date) and isinstance(rate_val, Decimal):
if isinstance(date_val, date) and isinstance(
rate_val, Decimal):
result.append({'date': date_val, 'rate': rate_val})
# date range
if max_date is None:
max_date = date_val
else :
else:
if max_date < date_val:
max_date = date_val
if min_date is None:
min_date = date_val
else :
else:
if min_date > date_val:
min_date = date_val
# rate range
if max_rate is None:
max_rate = rate_val
else :
else:
if max_rate < rate_val:
max_rate = rate_val
if min_rate is None:
min_rate = rate_val
else :
else:
if min_rate > rate_val:
min_rate = rate_val
else :
else:
raise UserError(gettext(
'investment.msg_err_unknown_content',
linetxt = line,
))
linetxt=line))
return (result, max_date, min_date)
# end ImportWizard

View file

@ -46,6 +46,14 @@ msgctxt "model:ir.message,text:msg_querytype_web"
msgid "Extract from web page"
msgstr "aus Webseite auslesen"
msgctxt "model:ir.message,text:msg_missing_url"
msgid "URL for the online source '%(oname)s' is missing."
msgstr "URL für die Onlinequelle '%(oname)s' fehlt."
msgctxt "model:ir.message,text:msg_bug_in_regexquery"
msgid "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgstr "Fehler in Regex-Code des Feldes '%(fname)s': %(errmsg)s [%(code)s]"
##############
# ir.ui.menu #
@ -330,6 +338,18 @@ msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mo - So"
msgctxt "field:investment.asset,updturl:"
msgid "URL"
msgstr "URL"
msgctxt "help:investment.asset,updturl:"
msgid "URL for data retrieval."
msgstr "URL für Datenabruf."
msgctxt "field:investment.asset,updturl_enable:"
msgid "URL required"
msgstr "URL required"
###############################
# investment.asset-source-rel #
@ -530,6 +550,14 @@ msgctxt "help:investment.source,query_method:"
msgid "Select the method to retrieve the data."
msgstr "Wählen Sie die Methode zum Abruf der Daten."
msgctxt "field:investment.source,fixed_url:"
msgid "Fixed URL"
msgstr "feste URL"
msgctxt "help:investment.source,fixed_url:"
msgid "URL must be defined at investment record."
msgstr "Die URL muss im Investitionsdatensatz definiert werden."
###################
# investment.rate #

View file

@ -34,6 +34,14 @@ msgctxt "model:ir.message,text:msg_querytype_web"
msgid "Extract from web page"
msgstr "Extract from web page"
msgctxt "model:ir.message,text:msg_missing_url"
msgid "URL for the online source '%(oname)s' is missing."
msgstr "URL for the online source '%(oname)s' is missing."
msgctxt "model:ir.message,text:msg_bug_in_regexquery"
msgid "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgstr "Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]"
msgctxt "model:ir.ui.menu,name:menu_investment"
msgid "Investment"
msgstr "Investment"
@ -290,6 +298,18 @@ msgctxt "selection:investment.asset,updtdays:"
msgid "Mon - Sun"
msgstr "Mon - Sun"
msgctxt "field:investment.asset,updturl:"
msgid "URL"
msgstr "URL"
msgctxt "help:investment.asset,updturl:"
msgid "URL for data retrieval."
msgstr "URL for data retrieval."
msgctxt "field:investment.asset,updturl_enable:"
msgid "URL required"
msgstr "URL required"
msgctxt "model:investment.asset_source_rel,name:"
msgid "Asset Source Relation"
msgstr "Asset Source Relation"
@ -482,6 +502,14 @@ msgctxt "help:investment.source,query_method:"
msgid "Select the method to retrieve the data."
msgstr "Select the method to retrieve the data."
msgctxt "field:investment.source,fixed_url:"
msgid "Fixed URL"
msgstr "Fixed URL"
msgctxt "help:investment.source,fixed_url:"
msgid "URL must be defined at investment record."
msgstr "URL must be defined at investment record."
msgctxt "model:investment.rate,name:"
msgid "Rate"
msgstr "Rate"

View file

@ -20,6 +20,12 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_querytype_web">
<field name="text">Extract from web page</field>
</record>
<record model="ir.message" id="msg_missing_url">
<field name="text">URL for the online source '%(oname)s' is missing.</field>
</record>
<record model="ir.message" id="msg_bug_in_regexquery">
<field name="text">Error in regex code of field '%(fname)s': %(errmsg)s [%(code)s]</field>
</record>
</data>
</tryton>

View file

@ -4,13 +4,17 @@
# full copyright notices and license terms.
from string import Template
import requests, logging, html2text, re
import requests
import logging
import html2text
import re
from datetime import datetime
from decimal import Decimal
from trytond.model import ModelView, ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import Eval, Bool
from trytond.i18n import gettext
from trytond.exceptions import UserError
logger = logging.getLogger(__name__)
@ -35,7 +39,8 @@ sel_rgxdatefmt = [
('%b %d %Y', 'mon dd yyyy'),
]
fields_check = ['url', 'nsin', 'isin', 'symbol', 'text', 'http_state', \
fields_check = [
'url', 'nsin', 'isin', 'symbol', 'text', 'http_state',
'fnddate', 'fndrate', 'fndident']
@ -51,32 +56,44 @@ class OnlineSource(ModelSQL, ModelView):
__name__ = 'investment.source'
name = fields.Char(string='Name', required=True)
query_method = fields.Selection(string='Method', required=True,
query_method = fields.Selection(
string='Method', required=True,
help='Select the method to retrieve the data.',
selection='get_query_methods')
url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB)
nohtml = fields.Boolean(string='Remove HTML',
fixed_url = fields.Boolean(
string='Fixed URL',
states={'invisible': Eval('query_method', '') != 'web'},
depends=DEPENDS_WEB,
help='URL must be defined at investment record.')
nohtml = fields.Boolean(
string='Remove HTML',
help='Removes HTML tags before the text is interpreted.',
states={
'invisible': STATES_WEB['invisible'],
}, depends=DEPENDS_WEB)
rgxdate = fields.Char(string='Date',
states={'invisible': STATES_WEB['invisible']},
depends=DEPENDS_WEB)
rgxdate = fields.Char(
string='Date',
help='Regex code to find the date in the downloaded HTML file.',
states=STATES_WEB, depends=DEPENDS_WEB)
rgxdatefmt = fields.Selection(string='Date format', selection=sel_rgxdatefmt,
rgxdatefmt = fields.Selection(
string='Date format', selection=sel_rgxdatefmt,
states=STATES_WEB, depends=DEPENDS_WEB)
rgxrate = fields.Char(string='Rate',
rgxrate = fields.Char(
string='Rate',
help='Regex code to find the rate in the downloaded HTML file.',
states=STATES_WEB, depends=DEPENDS_WEB)
rgxdecimal = fields.Selection(string='Decimal Separator',
help='Decimal separator for converting the market value into a number.',
rgxdecimal = fields.Selection(
string='Decimal Separator',
help='Decimal separator for converting the market ' +
'value into a number.',
selection=sel_rgxdecimal, states=STATES_WEB, depends=DEPENDS_WEB)
rgxident = fields.Char(string='Identifier',
rgxident = fields.Char(
string='Identifier',
help='Regex code to find the identifier in the downloaded HTML file.',
states={
'invisible': STATES_WEB['invisible'],
}, depends=DEPENDS_WEB)
rgxidtype = fields.Selection(string='ID-Type', selection=sel_rgxidtype,
states={'invisible': STATES_WEB['invisible']},
depends=DEPENDS_WEB)
rgxidtype = fields.Selection(
string='ID-Type', selection=sel_rgxidtype,
help='Type of identifier used to validate the result.',
states={
'required': Bool(Eval('rgxident', '')),
@ -84,27 +101,32 @@ class OnlineSource(ModelSQL, ModelView):
}, depends=DEPENDS_WEB+['rgxident'])
# field to test requests
used_url = fields.Function(fields.Char(string='Used URL', readonly=True,
used_url = fields.Function(fields.Char(
string='Used URL', readonly=True,
help='This URL is used to retrieve the HTML file.',
states={'invisible': STATES_WEB['invisible']}, depends=DEPENDS_WEB),
'on_change_with_used_url')
nsin = fields.Function(fields.Char(string='NSIN'),
'on_change_with_nsin', setter='set_test_value')
isin = fields.Function(fields.Char(string='ISIN'),
'on_change_with_isin', setter='set_test_value')
symbol = fields.Function(fields.Char(string='Symbol'),
'on_change_with_symbol', setter='set_test_value')
http_state = fields.Function(fields.Char(string='HTTP-State',
nsin = fields.Function(fields.Char(
string='NSIN'), 'on_change_with_nsin', setter='set_test_value')
isin = fields.Function(fields.Char(
string='ISIN'), 'on_change_with_isin', setter='set_test_value')
symbol = fields.Function(fields.Char(
string='Symbol'), 'on_change_with_symbol', setter='set_test_value')
http_state = fields.Function(fields.Char(
string='HTTP-State',
readonly=True), 'on_change_with_http_state')
text = fields.Function(fields.Text(string='Result',
readonly=True), 'on_change_with_text')
fnddate = fields.Function(fields.Date(string='Date', readonly=True,
text = fields.Function(fields.Text(
string='Result', readonly=True), 'on_change_with_text')
fnddate = fields.Function(fields.Date(
string='Date', readonly=True,
help='Date found during test query.'),
'on_change_with_fnddate')
fndrate = fields.Function(fields.Numeric(string='Rate', readonly=True,
help='Rate found during test query.', digits=(16,4)),
fndrate = fields.Function(fields.Numeric(
string='Rate', readonly=True,
help='Rate found during test query.', digits=(16, 4)),
'on_change_with_fndrate')
fndident = fields.Function(fields.Char(string='Identifier', readonly=True,
fndident = fields.Function(fields.Char(
string='Identifier', readonly=True,
help='Identifier found during test query.'),
'on_change_with_fndident')
@ -161,6 +183,12 @@ class OnlineSource(ModelSQL, ModelView):
"""
return True
@classmethod
def default_fixed_url(cls):
""" default: False
"""
return False
@fields.depends(*fields_check)
def on_change_nsin(self):
""" run request
@ -203,24 +231,22 @@ class OnlineSource(ModelSQL, ModelView):
def on_change_with_symbol(self, name=None):
return ''
@fields.depends('url', 'isin', 'nsin', 'symbol')
@fields.depends('url', 'isin', 'nsin', 'symbol', 'fixed_url')
def on_change_with_used_url(self, name=None):
""" get url for testing
"""
if self.url:
return self.get_url_with_parameter(
isin = self.isin,
nsin = self.nsin,
symbol = self.symbol,
)
isin=self.isin,
nsin=self.nsin,
symbol=self.symbol,
url=self.url)
@classmethod
def get_query_methods(cls):
""" get list of query-methods
"""
return [
('web', gettext('investment.msg_querytype_web')),
]
return [('web', gettext('investment.msg_querytype_web'))]
@classmethod
def set_test_value(cls, record, name, value):
@ -229,7 +255,16 @@ class OnlineSource(ModelSQL, ModelView):
pass
@classmethod
def run_query_method(cls, osource, isin, nsin, symbol, debug=False):
def validate(cls, records):
""" check regex-code
"""
for record in records:
for x in ['rgxdate', 'rgxrate', 'rgxident']:
if x:
record.get_regex_result('', x)
@classmethod
def run_query_method(cls, osource, isin, nsin, symbol, url, debug=False):
""" run selected query to retrive data
result: {
'text': raw-text from query - for debug,
@ -244,11 +279,11 @@ class OnlineSource(ModelSQL, ModelView):
if getattr(osource, 'query_method', None) == 'web':
return OSourc.read_from_website(
osource,
isin = isin,
nsin = nsin,
symbol = symbol,
debug = debug,
)
isin=isin,
nsin=nsin,
symbol=symbol,
debug=debug,
url=url)
def call_online_source(self):
""" use updated values to call online-source,
@ -256,24 +291,32 @@ class OnlineSource(ModelSQL, ModelView):
"""
OSourc = Pool().get('investment.source')
result = OSourc.run_query_method(self, self.isin, self.nsin,
result = OSourc.run_query_method(
self, self.isin, self.nsin, self.url,
self.symbol, debug=True)
if result is not None:
if result:
self.text = result.get('text', None)
self.http_state = result.get('http_state', None)
self.fnddate = result.get('date', None)
self.fndrate = result.get('rate', None)
self.fndident = result.get('code', None)
def get_url_with_parameter(self, isin=None, nsin=None, symbol=None):
def get_url_with_parameter(
self, isin=None, nsin=None, symbol=None, url=None):
""" generate url
"""
if self.fixed_url is True:
if not url:
raise UserError(gettext(
'investment.msg_missing_url',
oname=self.rec_name))
return url
if self.url:
return Template(self.url).substitute({
'isin': isin if isin is not None else '',
'nsin': nsin if nsin is not None else '',
'symbol': symbol if symbol is not None else '',
})
'symbol': symbol if symbol is not None else ''})
@classmethod
def update_rate(cls, asset):
@ -289,10 +332,10 @@ class OnlineSource(ModelSQL, ModelView):
for updtsource in asset.updtsources:
rate_data = cls.run_query_method(
updtsource,
isin = asset.isin,
nsin = asset.wkn,
symbol = asset.secsymb,
)
isin=asset.isin,
nsin=asset.wkn,
symbol=asset.secsymb,
url=asset.updturl)
if len(updtsource.rgxident or '') > 0:
# check result - same code?
@ -306,27 +349,25 @@ class OnlineSource(ModelSQL, ModelView):
if (asset_code or '').lower() != code.lower():
# fail
logger.warning(
'update_rate: got wrong code "%(wrong)s" - expected "%(exp)s"' % {
'update_rate: got wrong code ' +
'"%(wrong)s" - expected "%(exp)s"' % {
'exp': asset_code,
'wrong': code,
})
'wrong': code})
continue
to_create = {
'date': rate_data.get('date', None),
'rate': rate_data.get('rate', None),
'asset': asset.id,
}
'asset': asset.id}
if (to_create['date'] is not None) and \
(to_create['rate'] is not None):
# check if exists
if Rate.search_count([
('asset.id', '=', asset.id),
('date', '=', to_create['date']),
]) == 0:
('date', '=', to_create['date'])]) == 0:
Rate.create([to_create])
return True
else :
else:
# if we got a record for today - stop
# otherwise try next source
if to_create['date'] == IrDate.today():
@ -336,15 +377,24 @@ class OnlineSource(ModelSQL, ModelView):
def get_regex_result(self, html_text, field_name):
""" run regex on html-text, convert result
"""
OSource = Pool().get('investment.source')
rgxcode = getattr(self, field_name) or ''
if len(rgxcode) == 0:
return None
try:
search_result = re.compile(rgxcode).search(html_text)
if search_result is None:
return None
except Exception as e1:
raise UserError(gettext(
'investment.msg_bug_in_regexquery',
errmsg=str(e1),
fname=getattr(OSource, field_name).string,
code=rgxcode))
try :
try:
result = search_result.group(1)
except IndexError:
result = search_result.group(0)
@ -353,20 +403,23 @@ class OnlineSource(ModelSQL, ModelView):
dec_sep = [',', '.']
dec_sep.remove(self.rgxdecimal)
result = result.replace(dec_sep[0], '').replace(self.rgxdecimal, '.')
try :
result = result.replace(
dec_sep[0], '').replace(self.rgxdecimal, '.')
try:
result = Decimal(result)
except :
except Exception:
result = None
elif field_name == 'rgxdate':
try :
try:
result = datetime.strptime(result, self.rgxdatefmt).date()
except :
except Exception:
result = None
return result
@classmethod
def read_from_website(cls, updtsource, isin=None, nsin=None, symbol=None, debug=False):
def read_from_website(
cls, updtsource, isin=None, nsin=None,
symbol=None, url=None, debug=False):
""" read from url, extract values
"""
result = {}
@ -377,10 +430,10 @@ class OnlineSource(ModelSQL, ModelView):
res1 = requests.get(
updtsource.get_url_with_parameter(
isin = isin,
nsin = nsin,
symbol = symbol,
),
isin=isin,
nsin=nsin,
symbol=symbol,
url=url),
allow_redirects=True,
timeout=5.0)
@ -389,7 +442,7 @@ class OnlineSource(ModelSQL, ModelView):
'msg': res1.reason,
}
if res1.status_code in [200, 204]:
if res1.status_code in [200, 204, 410]:
html = res1.text
# remove html-tags
@ -409,8 +462,10 @@ class OnlineSource(ModelSQL, ModelView):
result['rate'] = updtsource.get_regex_result(html, 'rgxrate')
result['date'] = updtsource.get_regex_result(html, 'rgxdate')
result['code'] = updtsource.get_regex_result(html, 'rgxident')
else :
logger.error('read_from_website: %(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
else:
logger.error(
'read_from_website: ' +
'%(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
'code': res1.status_code,
'url': res1.url,
'redirects': ', '.join([x.url for x in res1.history]),

56
rate.py
View file

@ -3,33 +3,35 @@
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelView, ModelSQL, fields, Unique, Check, SymbolMixin
from trytond.model import (
ModelView, ModelSQL, fields, Unique, Check, SymbolMixin, Index)
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval, Bool
from trytond.pyson import Eval
class Rate(SymbolMixin, ModelSQL, ModelView):
'Rate'
__name__ = 'investment.rate'
asset = fields.Many2One(string='Asset', required=True,
select=True, ondelete='CASCADE',
asset = fields.Many2One(
string='Asset', required=True, ondelete='CASCADE',
model_name='investment.asset')
date = fields.Date(string='Date', required=True, select=True)
rate = fields.Numeric(string='Rate', required=True, select=True,
digits=(16, Eval('asset_digits', 4)),
depends=['asset_digits'])
date = fields.Date(string='Date', required=True)
rate = fields.Numeric(
string='Rate', required=True,
digits=(16, Eval('asset_digits', 4)), depends=['asset_digits'])
asset_digits = fields.Function(fields.Integer(string='Digits',
readonly=True), 'get_rate_data')
currency = fields.Function(fields.Many2One(string='Currency',
readonly=True, model_name='currency.currency'),
asset_digits = fields.Function(fields.Integer(
string='Digits', readonly=True), 'get_rate_data')
currency = fields.Function(fields.Many2One(
string='Currency', readonly=True, model_name='currency.currency'),
'get_rate_data')
uom = fields.Function(fields.Many2One(string='Uom',
readonly=True, model_name='product.uom'), 'get_rate_data')
symbol = fields.Function(fields.Char(string='Symbol',
readonly=True), 'get_rate_data')
uom = fields.Function(fields.Many2One(
string='Uom', readonly=True, model_name='product.uom'),
'get_rate_data')
symbol = fields.Function(fields.Char(
string='Symbol', readonly=True), 'get_rate_data')
@classmethod
def __setup__(cls):
@ -44,6 +46,21 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
'currency.msg_rate_positive'),
]
cls._order.insert(0, ('date', 'DESC'))
cls._sql_indexes.update({
Index(
t,
(t.date, Index.Range(order='DESC'))),
Index(
t,
(t.rate, Index.Range())),
Index(
t,
(t.asset, Index.Equality())),
Index(
t,
(t.asset, Index.Equality()),
(t.date, Index.Range(order='DESC'))),
})
@classmethod
def default_date(cls):
@ -62,8 +79,9 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
tab_rate = cls.__table__()
cursor = Transaction().connection.cursor()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset,
query = tab_asset.join(
tab_rate,
condition=tab_asset.id == tab_rate.asset,
).select(
tab_rate.id,
tab_asset.uom,
@ -74,7 +92,7 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in rates} for x in names}
result = {x: {y.id: None for y in rates} for x in names}
for record in records:
r1 = {
'symbol': '%',

View file

@ -2,7 +2,7 @@
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
from setuptools import setup
# To use a consistent encoding
from codecs import open
from os import path
@ -36,10 +36,10 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
l2 = i.strip().split(';')
if len(l2) < 4:
continue
modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]}
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
# tryton-version
major_version = 6
major_version = 7
minor_version = 0
requires = ['requests>=2.26', 'html2text']
@ -51,23 +51,27 @@ for dep in info.get('depends', []):
prefix = modversion[dep]['prefix']
if len(modversion[dep]['max']) > 0:
requires.append('%s_%s >= %s, <= %s' %
(prefix, dep, modversion[dep]['min'], modversion[dep]['max']))
else :
requires.append('%s_%s >= %s' %
(prefix, dep, modversion[dep]['min']))
else :
requires.append('%s_%s >= %s.%s, < %s.%s' %
('trytond', dep, major_version, minor_version,
requires.append('%s_%s >= %s, <= %s' % (
prefix, dep, modversion[dep]['min'],
modversion[dep]['max']))
else:
requires.append('%s_%s >= %s' % (
prefix, dep, modversion[dep]['min']))
else:
requires.append('%s_%s >= %s.%s, < %s.%s' % (
'trytond', dep, major_version, minor_version,
major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' % (
major_version, minor_version, major_version, minor_version + 1))
setup(name='%s_%s' % (PREFIX, MODULE),
setup(
name='%s_%s' % (PREFIX, MODULE),
version=info.get('version', '0.0.1'),
description='Tryton module to add investment items.',
long_description=long_description,
long_description_content_type='text/x-rst',
url='https://www.m-ds.de/',
download_url='https://scmdev.m-ds.de/Tryton/Extra/investment',
author='martin-data services',
author_email='service@m-ds.de',
license='GPL-3',
@ -87,6 +91,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
'License :: OSI Approved :: GNU General Public License (GPL)',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
keywords='tryton investment shares commodities',
@ -95,8 +100,8 @@ setup(name='%s_%s' % (PREFIX, MODULE),
'trytond.modules.%s' % MODULE,
],
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
'trytond.modules.%s' % MODULE: (info.get('xml', []) + [
'tryton.cfg', 'locale/*.po', 'tests/*.py',
'view/*.xml', 'icon/*.svg', 'scripts/*.py',
'versiondep.txt', 'README.rst']),
},

View file

@ -47,6 +47,17 @@ full copyright notices and license terms. -->
<field name="rgxrate">\nKurs\s+([\d+\.]*\d+,\d+)\s+USD\s*\n</field>
<field name="rgxdecimal">,</field>
</record>
<record model="investment.source" id="web_finanzen_aktie">
<field name="name">www.finanzen.net - Aktie</field>
<field name="url">https://www.finanzen.net/aktien/${symbol}</field>
<field name="nohtml" eval="True"/>
<field name="rgxdate">\nKurszeit(\d+\.\d+\.\d+) \d{2}:\d{2}:\d{2}.*\n</field>
<field name="rgxdatefmt">%d.%m.%Y</field>
<field name="rgxrate">\nKurs(\d+,\d+) EUR.*\n</field>
<field name="rgxident">\nISIN([A-Z,0-9]+).*\n</field>
<field name="rgxidtype">isin</field>
<field name="rgxdecimal">,</field>
</record>
<record model="investment.source" id="web_markets_ft_com">
<field name="name">Financial Times UK</field>
<field name="url">https://markets.ft.com/data/etfs/tearsheet/summary?s=${symbol}</field>
@ -60,13 +71,25 @@ full copyright notices and license terms. -->
<field name="name">www.sbroker.de</field>
<field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field>
<field name="nohtml" eval="True"/>
<field name="rgxdate">\nDatum / Uhrzeit: (\d+\.\d+\.\d+) / \d+:\d+\s+\n</field>
<field name="rgxdate">\nDatum\s*(?:\/ Uhrzeit)*: (\d+\.\d+\.\d+)\s*(?:\/ \d+:\d+)*\s*\n</field>
<field name="rgxdatefmt">%d.%m.%y</field>
<field name="rgxrate">Kurs aktuell .* (\d+,\d+)\s+EUR.*\n</field>
<field name="rgxrate">(?:Kurs aktuell|Rucknahmepreis)\s+[()\w\./\[\]!]+\s+(\d+,\d+)\s+(EUR|€)</field>
<field name="rgxdecimal">,</field>
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
<field name="rgxidtype">isin</field>
</record>
<record model="investment.source" id="web_faz">
<field name="name">FAZ</field>
<field name="url">https://www.faz.net/aktuell/finanzen/kurs/etf/</field>
<field name="nohtml" eval="True"/>
<field name="fixed_url" eval="True"/>
<field name="rgxdate">(\d{2}\.\d{2}\.\d{4})</field>
<field name="rgxdatefmt">%d.%m.%y</field>
<field name="rgxrate">\nKurs(\d+.\d+)\s+€\s+\n</field>
<field name="rgxdecimal">,</field>
<field name="rgxident">\nWKN:.*\sISIN: ([A-Z,0-9]+).*\n</field>
<field name="rgxidtype">isin</field>
</record>
</data>
</tryton>

View file

@ -1,30 +1,2 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import trytond.tests.test_tryton
import unittest
from trytond.modules.investment.tests.test_asset import AssetTestCase
from trytond.modules.investment.tests.test_rate import RateTestCase
from trytond.modules.investment.tests.test_source import SourceTestCase
from trytond.modules.investment.tests.test_wizard import WizardTestCase
__all__ = ['suite']
class InvestmentTestCase(\
WizardTestCase, \
SourceTestCase, \
RateTestCase,\
AssetTestCase,\
):
'Test investment module'
module = 'investment'
# end InvestmentTestCase
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(InvestmentTestCase))
return suite

770
tests/asset.py Normal file
View file

@ -0,0 +1,770 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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.modules.company.tests import create_company
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from decimal import Decimal
from datetime import time, date, datetime
class AssetTestCase(object):
""" test asset
"""
def prep_asset_company(self):
""" get/create company
"""
Company = Pool().get('company.company')
company = Company.search([])
if len(company) > 0:
company = company[0]
else:
company = create_company(name='m-ds')
return company
def prep_asset_product(
self, name='Product 1', description=None, unit='u',
unit_name='Units'):
""" create product
"""
pool = Pool()
Product = pool.get('product.template')
Uom = pool.get('product.uom')
uom, = Uom.search([('symbol', '=', unit)])
prod_templ, = Product.create([{
'name': name,
'type': 'assets',
'list_price': Decimal('1.0'),
'default_uom': uom.id,
'products': [('create', [{
'description': description,
}])],
}])
self.assertEqual(prod_templ.default_uom.symbol, unit)
self.assertEqual(prod_templ.products[0].description, description)
return prod_templ.products[0]
def prep_asset_item(self, company, product):
""" create asset
"""
pool = Pool()
Asset = pool.get('investment.asset')
asset, = Asset.create([{
'company': company.id,
'product': product.id,
'currency': company.currency.id,
'currency_digits': 4,
'uom': product.default_uom.id,
}])
self.assertEqual(asset.rec_name, '%s | - usd/%s | -' % (
product.rec_name,
asset.uom.symbol,
))
self.assertEqual(asset.currency.rec_name, 'usd')
self.assertEqual(asset.currency_digits, 4)
self.assertEqual(asset.product.rec_name, product.name)
self.assertEqual(asset.uom.symbol, product.default_uom.symbol)
return asset
@with_transaction()
def test_asset_create(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product=product)
self.assertEqual(asset.symbol, 'usd/u')
self.assertEqual(asset.asset_symbol.symbol, 'usd/u')
# check ranges
Asset.write(*[
[asset],
{
'currency_digits': 1,
}])
self.assertRaisesRegex(
UserError,
'ss',
Asset.write,
*[[asset], {
'currency_digits': -1,
}])
@with_transaction()
def test_asset_rec_name(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product=product)
self.assertEqual(asset.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
}])
self.assertEqual(
asset.rec_name,
'Product 1 | 2.4500 usd/u | 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()
with Transaction().set_context({'company': company.id}):
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/u | 05/18/2022')
self.assertEqual(
asset2.rec_name,
'Product 2 | 2.6000 usd/u | 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
specific date - fixed date
"""
Asset = Pool().get('investment.asset')
cursor = Transaction().connection.cursor()
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(company=company, product=product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 3),
'rate': Decimal('3.6'),
}])],
},
])
self.assertEqual(
asset1.rec_name,
'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 5, 12))
self.assertEqual(asset1.rates[3].date, date(2022, 5, 3))
# query fixed date
tab_percent = Asset.get_percentage_sql(days=0)
with Transaction().set_context({
'qdate': date(2022, 5, 16)}):
query = tab_percent.select(
tab_percent.id,
tab_percent.date,
tab_percent.percent,
where=tab_percent.id == asset1.id,
)
cursor.execute(*query)
records = cursor.fetchall()
# there should be one record, three colums
self.assertEqual(len(records), 1)
self.assertEqual(len(records[0]), 3)
self.assertEqual(records[0][0], asset1.id)
self.assertEqual(records[0][1], date(2022, 5, 16))
self.assertEqual(
records[0][2].quantize(Decimal('0.01')),
Decimal('6.12'))
@with_transaction()
def test_asset_percentages_daterange(self):
""" create asset, add rates, check selection of
value
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(company=company, product=product)
asset2 = self.prep_asset_item(company=company, product=product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
self.assertEqual(asset2.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 14),
'rate': Decimal('5.75'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('5.25'),
}])],
},
])
self.assertEqual(
asset1.rec_name,
'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(
asset2.rec_name,
'Product 1 | 5.2500 usd/u | 05/15/2022')
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset2.change_day1, Decimal('-8.7'))
self.assertEqual(asset1.change_month1, None)
self.assertEqual(asset2.change_month1, None)
self.assertEqual(asset1.change_month3, None)
self.assertEqual(asset2.change_month3, None)
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset2.change_month6, None)
self.assertEqual(asset1.change_month12, None)
self.assertEqual(asset2.change_month12, None)
# check ordering
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('-8.7'))
self.assertEqual(assets[1].change_day1, Decimal('6.12'))
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('6.12'))
self.assertEqual(assets[1].change_day1, Decimal('-8.7'))
# check 5-day-range
# four days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 12),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 12))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# five days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 11),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 11))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# six days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 10),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 10))
self.assertEqual(asset1.change_day1, None)
@with_transaction()
def test_asset_percentges_values(self):
""" create asset, add rates, check percentages
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(company=company, product=product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 4, 14),
'rate': Decimal('2.2'),
}, {
'date': date(2022, 2, 14),
'rate': Decimal('2.8'),
},])],
}])
self.assertEqual(
asset1.rec_name,
'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 4, 14))
self.assertEqual(asset1.rates[3].date, date(2022, 2, 14))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset1.change_month1, Decimal('18.18'))
self.assertEqual(asset1.change_month3, Decimal('-7.14'))
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset1.change_month12, None)
# call order-functions
Asset.search([], order=[('change_day1', 'ASC')])
Asset.search([], order=[('change_month1', 'ASC')])
Asset.search([], order=[('change_month3', 'ASC')])
Asset.search([], order=[('change_month6', 'ASC')])
Asset.search([], order=[('change_month12', 'ASC')])
# searcher
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.1'))]),
1)
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.15'))]),
0)
self.assertEqual(
Asset.search_count([('change_day1', '=', Decimal('6.12'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.0'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.18'))]),
0)
self.assertEqual(
Asset.search_count([('change_month1', '=', Decimal('18.18'))]),
1)
self.assertEqual(
Asset.search_count([('change_month3', '=', Decimal('-7.14'))]),
1)
self.assertEqual(
Asset.search_count([('change_month6', '=', None)]),
1)
@with_transaction()
def test_asset_check_onlinesource_onoff(self):
""" create asset, switch online-source on/off
"""
pool = Pool()
OnlineSource = pool.get('investment.source')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(company=company, product=product)
o_source, = OnlineSource.create([{
'name': 'Source 1',
}])
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, time(14, 0))
asset.updtsources = [o_source]
asset.updttime = time(10, 45)
asset.save()
self.assertEqual(len(asset.updtsources), 1)
self.assertEqual(asset.updtsources[0].rec_name, 'Source 1')
self.assertEqual(asset.updttime, time(10, 45))
asset.updtsources = []
asset.on_change_updtsources()
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, None)
@with_transaction()
def test_asset_check_update_select(self):
""" create asset, add online-source,
check selection of assets to update
"""
pool = Pool()
OnlineSource = pool.get('investment.source')
Asset = pool.get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(company=company, product=product)
o_source, = OnlineSource.create([{
'name': 'Source 1',
}])
Asset.write(*[
[asset],
{
'updtsources': [('add', [o_source.id])],
'updttime': time(10, 45),
}])
with Transaction().set_context({'qdate': date(2022, 10, 14)}):
# re-read to make context work
asset2, = Asset.browse([asset.id])
self.assertEqual(len(asset2.updtsources), 1)
self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1')
self.assertEqual(asset2.updttime, time(10, 45))
self.assertEqual(len(asset2.rates), 0)
# 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, 17, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([
('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]),
1)
# add rate at next monday
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 10, 17), # monday
'rate': Decimal('1.5'),
}])],
}])
self.assertEqual(len(asset.rates), 1)
asset2, = Asset.browse([asset.id])
self.assertEqual(asset.updtsources[0].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, 17))
self.assertEqual(asset.nextupdate, datetime(2022, 10, 18, 10, 45))
self.assertEqual(
Asset.search_count([
('nextupdate', '<', datetime(2022, 10, 18, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([
('nextupdate', '>=', datetime(2022, 10, 18, 10, 45))]),
1)
# add rate at today
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 10, 18),
'rate': Decimal('1.5'),
}])],
}])
self.assertEqual(len(asset.rates), 2)
asset2, = Asset.browse([asset.id])
self.assertEqual(asset2.updtsources[0].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, 18))
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 19, 10, 45))
self.assertEqual(
Asset.search_count([
('nextupdate', '<', datetime(2022, 10, 19, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([
('nextupdate', '>=', datetime(2022, 10, 19, 10, 45))]),
1)
@with_transaction()
def test_asset_indentifiers(self):
""" create asset, add identifiers
"""
pool = Pool()
Product = pool.get('product.product')
Asset = pool.get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product1 = self.prep_asset_product(
name='Product unit', unit='u')
product2 = self.prep_asset_product(
name='Product gram', unit='g')
asset1 = self.prep_asset_item(company=company, product=product1)
asset2 = self.prep_asset_item(company=company, product=product2)
Product.write(*[
[product1],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965515',
}, {
'type': 'secsymb',
'code': '1472977',
}, {
'type': 'isin',
'code': 'XC0009655157',
}, ])],
},
[product2],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965310',
}, {
'type': 'secsymb',
'code': '1431157',
}, {
'type': 'isin',
'code': 'XC0009653103',
}, ])],
},
])
self.assertEqual(asset1.wkn, '965515')
self.assertEqual(asset1.isin, 'XC0009655157')
self.assertEqual(asset1.secsymb, '1472977')
self.assertEqual(
Asset.search_count([('wkn', '=', '965515')]),
1)
self.assertEqual(
Asset.search_count([('isin', '=', 'XC0009655157')]),
1)
self.assertEqual(
Asset.search_count([('secsymb', '=', '1472977')]),
1)
self.assertEqual(
Asset.search_count([('rec_name', '=', '965515')]),
1)
self.assertEqual(
Asset.search_count([('rec_name', '=', 'XC0009655157')]),
1)
self.assertEqual(
Asset.search_count([('rec_name', '=', '1472977')]),
1)
self.assertEqual(
Asset.search_count([('name', '=', '965515')]),
1)
self.assertEqual(
Asset.search_count([('name', '=', 'XC0009655157')]),
1)
self.assertEqual(
Asset.search_count([('name', '=', '1472977')]),
1)
self.assertEqual(
Asset.search_count([('name', '=', 'Product unit')]),
1)
self.assertEqual(Asset.search_count([
('wkn', 'ilike', '9655%'),
]), 1)
self.assertEqual(Asset.search_count([
('wkn', 'ilike', '965%'),
]), 2)
self.assertEqual(asset2.wkn, '965310')
self.assertEqual(asset2.isin, 'XC0009653103')
self.assertEqual(asset2.secsymb, '1431157')
# order wkn
assets = Asset.search([], order=[('wkn', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965310')
self.assertEqual(assets[1].wkn, '965515')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965515')
self.assertEqual(assets[1].wkn, '965310')
# order isin
assets = Asset.search([], order=[('isin', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009653103')
self.assertEqual(assets[1].isin, 'XC0009655157')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009655157')
self.assertEqual(assets[1].isin, 'XC0009653103')
# order secsymb
assets = Asset.search([], order=[('secsymb', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1431157')
self.assertEqual(assets[1].secsymb, '1472977')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1472977')
self.assertEqual(assets[1].secsymb, '1431157')
@with_transaction()
def test_asset_check_product_update(self):
""" check update of product on asset
"""
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product1 = self.prep_asset_product(
name='Product unit', unit='u')
product2 = self.prep_asset_product(
name='Product gram', unit='g')
self.assertEqual(product2.default_uom.digits, 2)
asset = self.prep_asset_item(company=company, product=product1)
self.assertEqual(asset.product.rec_name, 'Product unit')
self.assertEqual(asset.product.default_uom.rec_name, 'Unit')
self.assertEqual(asset.uom.rec_name, 'Unit')
self.assertEqual(asset.currency_digits, 4)
asset.product = product2
asset.on_change_product()
asset.save()
self.assertEqual(asset.product.rec_name, 'Product gram')
self.assertEqual(asset.product.default_uom.rec_name, 'Gram')
self.assertEqual(asset.uom.rec_name, 'Gram')
asset.on_change_currency()
asset.save()
self.assertEqual(asset.currency_digits, 2)
# end AssetTestCase

50
tests/rate.py Normal file
View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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.transaction import Transaction
from trytond.pool import Pool
from decimal import Decimal
from datetime import date
class RateTestCase(object):
""" test rate
"""
@with_transaction()
def test_rate_create(self):
""" create rate
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(company=company, product=product)
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(len(asset.rates), 2)
self.assertEqual(asset.rates[0].date, date(2022, 5, 2))
self.assertEqual(asset.rates[0].rate, Decimal('2.8'))
self.assertEqual(asset.rates[0].uom.rec_name, 'Unit')
self.assertEqual(asset.rates[0].asset_digits, 4)
self.assertEqual(asset.rates[0].currency.rec_name, 'usd')
self.assertEqual(asset.rates[0].symbol, '%')
self.assertEqual(asset.change_symbol.symbol, '%')
# end RateTestCase

185
tests/source.py Normal file
View file

@ -0,0 +1,185 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 trytond.exceptions import UserError
from decimal import Decimal
from datetime import time, date, datetime
from unittest.mock import MagicMock
from requests import Response
import requests
class SourceTestCase(object):
""" test online source
"""
@with_transaction()
def test_waitlist_source_request(self):
""" create source, call server
"""
pool = Pool()
OSource = pool.get('investment.source')
Asset = pool.get('investment.asset')
Product = pool.get('product.product')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
osource, = OSource.create([{
'name': 'Source 1',
'url': 'https://foo.bar/${isin}/${nsin}/${symbol}',
'rgxdate': 'Course Date (\\d+.\\d+.\\d+) Today',
'rgxdatefmt': '%d.%m.%Y',
'rgxrate': 'High (\\d+,\\d+) EUR',
'rgxdecimal': ',',
}])
self.assertEqual(osource.rec_name, 'Source 1')
product = self.prep_asset_product(
name='Product 1',
description='some asset')
Product.write(*[
[product],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965515',
}, {
'type': 'secsymb',
'code': '1472977',
}, {
'type': 'isin',
'code': 'XC0009655157',
}, ])],
}])
asset = self.prep_asset_item(company=company, product=product)
Asset.write(*[
[asset],
{
'updtsources': [('add', [osource.id])],
}])
with Transaction().set_context({
'qdate': date(2022, 10, 1), # saturday
'qdatetime': datetime(2022, 10, 2, 10, 0, 0)}):
asset2, = Asset.browse([asset])
self.assertEqual(asset2.wkn, '965515')
self.assertEqual(asset2.isin, 'XC0009655157')
self.assertEqual(asset2.secsymb, '1472977')
self.assertEqual(asset2.updttime, time(14, 0))
self.assertEqual(len(asset2.updtsources), 1)
self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1')
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 = """<html><body>Response from finance-server
Course Date 14.08.2022 Today
High 34,87 EUR
</body></html>""".encode('utf8')
resp1.status_code = 200
resp1.reason = 'OK'
requests.get = MagicMock(return_value=resp1)
OSource.update_rate(asset)
self.assertEqual(len(asset.rates), 1)
self.assertEqual(asset.rates[0].date, date(2022, 8, 14))
self.assertEqual(asset.rates[0].rate, Decimal('34.87'))
@with_transaction()
def test_waitlist_source_check_regex(self):
""" create source, check convert
"""
pool = Pool()
OSource = pool.get('investment.source')
osource, = OSource.create([{
'name': 'Source 1',
'rgxdate': 'Course Date (\\d+.\\d+.\\d+) Today',
'rgxdatefmt': '%d.%m.%Y',
'rgxrate': 'High (\\d+,\\d+) EUR',
'rgxdecimal': ',',
}])
self.assertEqual(osource.rec_name, 'Source 1')
self.assertEqual(osource.get_regex_result(
'The Course Date 14.03.2022 Today, High 13,43 EUR',
'rgxdate'
), date(2022, 3, 14))
self.assertEqual(osource.get_regex_result(
'The Course Date 14.03.2022 Today, High 13,43 EUR',
'rgxrate'
), Decimal('13.43'))
# iso-date
OSource.write(*[
[osource],
{
'rgxdate': 'Course Date (\\d+-\\d+-\\d+) Today',
'rgxdatefmt': '%Y-%m-%d',
}])
self.assertEqual(osource.get_regex_result(
'The Course Date 2022-03-14 Today, High 13,43 EUR',
'rgxdate'
), date(2022, 3, 14))
@with_transaction()
def test_waitlist_source_check_regex_validate(self):
""" create source, check validation of regex-code
"""
pool = Pool()
OSource = pool.get('investment.source')
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Date': nothing to repeat " +
r"at position 0 \[\*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check date',
'rgxdate': '** multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert ** multiple repeat',
}])
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Rate': multiple repeat " +
r"at position 6 \[rate \*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check rate',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate ** multiple repeat',
'rgxident': 'identifiert -- multiple repeat',
}])
self.assertRaisesRegex(
UserError,
r"Error in regex code of field 'Identifier': multiple " +
r"repeat at position 13 \[identifiert \*+ multiple repeat\]",
OSource.create,
[{
'name': 'Check rgxident',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert ** multiple repeat',
}])
OSource.create([{
'name': 'Check rgxident',
'rgxdate': '-- multiple repeat',
'rgxrate': 'rate -- multiple repeat',
'rgxident': 'identifiert -- multiple repeat',
}])
# end SourceTestCase

View file

@ -1,739 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from decimal import Decimal
from datetime import time, date, datetime
class AssetTestCase(ModuleTestCase):
'Test asset module'
module = 'investment'
def prep_asset_company(self):
""" get/create company
"""
Company = Pool().get('company.company')
company = Company.search([])
if len(company) > 0:
company = company[0]
else :
company = create_company(name='m-ds')
return company
def prep_asset_product(self, name='Product 1', description=None, unit='u', unit_name='Units'):
""" create product
"""
pool = Pool()
Product = pool.get('product.template')
Uom = pool.get('product.uom')
uom, = Uom.search([('symbol', '=', unit)])
prod_templ, = Product.create([{
'name': name,
'type': 'assets',
'list_price': Decimal('1.0'),
'default_uom': uom.id,
'products': [('create', [{
'description': description,
}])],
}])
self.assertEqual(prod_templ.default_uom.symbol, unit)
self.assertEqual(prod_templ.products[0].description, description)
return prod_templ.products[0]
def prep_asset_item(self, company, product):
""" create asset
"""
pool = Pool()
Asset = pool.get('investment.asset')
asset, = Asset.create([{
'company': company.id,
'product': product.id,
'currency': company.currency.id,
'currency_digits': 4,
'uom': product.default_uom.id,
}])
self.assertEqual(asset.rec_name, '%s | - usd/%s | -' % (
product.rec_name,
asset.uom.symbol,
))
self.assertEqual(asset.currency.rec_name, 'usd')
self.assertEqual(asset.currency_digits, 4)
self.assertEqual(asset.product.rec_name, product.name)
self.assertEqual(asset.uom.symbol, product.default_uom.symbol)
return asset
@with_transaction()
def test_asset_create(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset.symbol, 'usd/u')
self.assertEqual(asset.asset_symbol.symbol, 'usd/u')
# check ranges
Asset.write(*[
[asset],
{
'currency_digits': 1,
}])
self.assertRaisesRegex(UserError,
'ss',
Asset.write,
*[[asset], {
'currency_digits': -1,
}])
@with_transaction()
def test_asset_rec_name(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
}])
self.assertEqual(asset.rec_name, 'Product 1 | 2.4500 usd/u | 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/u | 05/18/2022')
self.assertEqual(asset2.rec_name, 'Product 2 | 2.6000 usd/u | 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
specific date - fixed date
"""
Asset = Pool().get('investment.asset')
cursor = Transaction().connection.cursor()
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 3),
'rate': Decimal('3.6'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 5, 12))
self.assertEqual(asset1.rates[3].date, date(2022, 5, 3))
# query fixed date
tab_percent = Asset.get_percentage_sql(days=0)
with Transaction().set_context({
'qdate': date(2022, 5, 16),
}):
query = tab_percent.select(
tab_percent.id,
tab_percent.date,
tab_percent.percent,
where=tab_percent.id==asset1.id,
)
cursor.execute(*query)
records = cursor.fetchall()
# there should be one record, three colums
self.assertEqual(len(records), 1)
self.assertEqual(len(records[0]), 3)
self.assertEqual(records[0][0], asset1.id)
self.assertEqual(records[0][1], date(2022, 5, 16))
self.assertEqual(records[0][2].quantize(Decimal('0.01')), Decimal('6.12'))
@with_transaction()
def test_asset_percentages_daterange(self):
""" create asset, add rates, check selection of
value
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
asset2 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
self.assertEqual(asset2.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 14),
'rate': Decimal('5.75'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('5.25'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(asset2.rec_name, 'Product 1 | 5.2500 usd/u | 05/15/2022')
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset2.change_day1, Decimal('-8.7'))
self.assertEqual(asset1.change_month1, None)
self.assertEqual(asset2.change_month1, None)
self.assertEqual(asset1.change_month3, None)
self.assertEqual(asset2.change_month3, None)
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset2.change_month6, None)
self.assertEqual(asset1.change_month12, None)
self.assertEqual(asset2.change_month12, None)
# check ordering
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('-8.7'))
self.assertEqual(assets[1].change_day1, Decimal('6.12'))
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('6.12'))
self.assertEqual(assets[1].change_day1, Decimal('-8.7'))
# check 5-day-range
# four days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 12),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 12))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# five days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 11),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 11))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# six days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 10),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 10))
self.assertEqual(asset1.change_day1, None)
@with_transaction()
def test_asset_percentges_values(self):
""" create asset, add rates, check percentages
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 4, 14),
'rate': Decimal('2.2'),
}, {
'date': date(2022, 2, 14),
'rate': Decimal('2.8'),
},])],
}])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 4, 14))
self.assertEqual(asset1.rates[3].date, date(2022, 2, 14))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset1.change_month1, Decimal('18.18'))
self.assertEqual(asset1.change_month3, Decimal('-7.14'))
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset1.change_month12, None)
# call order-functions
Asset.search([], order=[('change_day1', 'ASC')])
Asset.search([], order=[('change_month1', 'ASC')])
Asset.search([], order=[('change_month3', 'ASC')])
Asset.search([], order=[('change_month6', 'ASC')])
Asset.search([], order=[('change_month12', 'ASC')])
# searcher
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.1'))]),
1)
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.15'))]),
0)
self.assertEqual(
Asset.search_count([('change_day1', '=', Decimal('6.12'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.0'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.18'))]),
0)
self.assertEqual(
Asset.search_count([('change_month1', '=', Decimal('18.18'))]),
1)
self.assertEqual(
Asset.search_count([('change_month3', '=', Decimal('-7.14'))]),
1)
self.assertEqual(
Asset.search_count([('change_month6', '=', None)]),
1)
@with_transaction()
def test_asset_check_onlinesource_onoff(self):
""" create asset, switch online-source on/off
"""
pool = Pool()
OnlineSource = pool.get('investment.source')
Asset = pool.get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
o_source, = OnlineSource.create([{
'name': 'Source 1',
}])
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, time(14,0))
asset.updtsources = [o_source]
asset.updttime = time(10, 45)
asset.save()
self.assertEqual(len(asset.updtsources), 1)
self.assertEqual(asset.updtsources[0].rec_name, 'Source 1')
self.assertEqual(asset.updttime, time(10, 45))
asset.updtsources = []
asset.on_change_updtsources()
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, None)
@with_transaction()
def test_asset_check_update_select(self):
""" create asset, add online-source,
check selection of assets to update
"""
pool = Pool()
OnlineSource = pool.get('investment.source')
Asset = pool.get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
o_source, = OnlineSource.create([{
'name': 'Source 1',
}])
Asset.write(*[
[asset],
{
'updtsources': [('add', [o_source.id])],
'updttime': time(10, 45),
}])
with Transaction().set_context({
'qdate': date(2022, 10, 14), # friday
}):
# re-read to make context work
asset2, = Asset.browse([asset.id])
self.assertEqual(len(asset2.updtsources), 1)
self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1')
self.assertEqual(asset2.updttime, time(10, 45))
self.assertEqual(len(asset2.rates), 0)
# 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, 17, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]),
1)
# add rate at next monday
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 10, 17), # monday
'rate': Decimal('1.5'),
}])],
}])
self.assertEqual(len(asset.rates), 1)
asset2, = Asset.browse([asset.id])
self.assertEqual(asset.updtsources[0].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, 17))
self.assertEqual(asset.nextupdate, datetime(2022, 10, 18, 10, 45))
self.assertEqual(
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 18, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 18, 10, 45))]),
1)
# add rate at today
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 10, 18),
'rate': Decimal('1.5'),
}])],
}])
self.assertEqual(len(asset.rates), 2)
asset2, = Asset.browse([asset.id])
self.assertEqual(asset2.updtsources[0].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, 18))
self.assertEqual(asset2.nextupdate, datetime(2022, 10, 19, 10, 45))
self.assertEqual(
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 19, 10, 45))]),
0)
self.assertEqual(
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 19, 10, 45))]),
1)
@with_transaction()
def test_asset_indentifiers(self):
""" create asset, add identifiers
"""
pool = Pool()
Product = pool.get('product.product')
Asset = pool.get('investment.asset')
company = self.prep_asset_company()
product1 = self.prep_asset_product(
name='Product unit', unit='u')
product2 = self.prep_asset_product(
name='Product gram', unit='g')
asset1 = self.prep_asset_item(
company=company,
product = product1)
asset2 = self.prep_asset_item(
company=company,
product = product2)
Product.write(*[
[product1],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965515',
}, {
'type': 'secsymb',
'code': '1472977',
}, {
'type': 'isin',
'code': 'XC0009655157',
}, ])],
},
[product2],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965310',
}, {
'type': 'secsymb',
'code': '1431157',
}, {
'type': 'isin',
'code': 'XC0009653103',
}, ])],
},
])
self.assertEqual(asset1.wkn, '965515')
self.assertEqual(asset1.isin, 'XC0009655157')
self.assertEqual(asset1.secsymb, '1472977')
self.assertEqual(Asset.search_count([('wkn', '=', '965515')]), 1)
self.assertEqual(Asset.search_count([('isin', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('secsymb', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', '965515')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('rec_name', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('name', '=', '965515')]), 1)
self.assertEqual(Asset.search_count([('name', '=', 'XC0009655157')]), 1)
self.assertEqual(Asset.search_count([('name', '=', '1472977')]), 1)
self.assertEqual(Asset.search_count([('name', '=', 'Product unit')]), 1)
self.assertEqual(Asset.search_count([
('wkn', 'ilike', '9655%'),
]), 1)
self.assertEqual(Asset.search_count([
('wkn', 'ilike', '965%'),
]), 2)
self.assertEqual(asset2.wkn, '965310')
self.assertEqual(asset2.isin, 'XC0009653103')
self.assertEqual(asset2.secsymb, '1431157')
# order wkn
assets = Asset.search([], order=[('wkn', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965310')
self.assertEqual(assets[1].wkn, '965515')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].wkn, '965515')
self.assertEqual(assets[1].wkn, '965310')
# order isin
assets = Asset.search([], order=[('isin', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009653103')
self.assertEqual(assets[1].isin, 'XC0009655157')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].isin, 'XC0009655157')
self.assertEqual(assets[1].isin, 'XC0009653103')
# order secsymb
assets = Asset.search([], order=[('secsymb', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1431157')
self.assertEqual(assets[1].secsymb, '1472977')
assets = Asset.search([], order=[('wkn', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].secsymb, '1472977')
self.assertEqual(assets[1].secsymb, '1431157')
@with_transaction()
def test_asset_check_product_update(self):
""" check update of product on asset
"""
company = self.prep_asset_company()
product1 = self.prep_asset_product(
name='Product unit', unit='u')
product2 = self.prep_asset_product(
name='Product gram', unit='g')
self.assertEqual(product2.default_uom.digits, 2)
asset = self.prep_asset_item(
company=company,
product = product1)
self.assertEqual(asset.product.rec_name, 'Product unit')
self.assertEqual(asset.product.default_uom.rec_name, 'Unit')
self.assertEqual(asset.uom.rec_name, 'Unit')
self.assertEqual(asset.currency_digits, 4)
asset.product = product2
asset.on_change_product()
asset.save()
self.assertEqual(asset.product.rec_name, 'Product gram')
self.assertEqual(asset.product.default_uom.rec_name, 'Gram')
self.assertEqual(asset.uom.rec_name, 'Gram')
asset.on_change_currency()
asset.save()
self.assertEqual(asset.currency_digits, 2)
# end AssetTestCase

26
tests/test_module.py Normal file
View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 ModuleTestCase
from .asset import AssetTestCase
from .rate import RateTestCase
from .source import SourceTestCase
from .wizard import WizardTestCase
class InvestmentTestCase(
WizardTestCase,
SourceTestCase,
RateTestCase,
AssetTestCase,
ModuleTestCase):
'Test investment module'
module = 'investment'
# end InvestmentTestCase
del ModuleTestCase

View file

@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.modules.company.tests import create_company
from decimal import Decimal
from datetime import date
class RateTestCase(ModuleTestCase):
'Test rate module'
module = 'investment'
@with_transaction()
def test_rate_create(self):
""" create rate
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
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(len(asset.rates), 2)
self.assertEqual(asset.rates[0].date, date(2022, 5, 2))
self.assertEqual(asset.rates[0].rate, Decimal('2.8'))
self.assertEqual(asset.rates[0].uom.rec_name, 'Unit')
self.assertEqual(asset.rates[0].asset_digits, 4)
self.assertEqual(asset.rates[0].currency.rec_name, 'usd')
self.assertEqual(asset.rates[0].symbol, '%')
self.assertEqual(asset.change_symbol.symbol, '%')
# end RateTestCase

View file

@ -1,139 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction
from decimal import Decimal
from datetime import time, date, datetime
from unittest.mock import MagicMock
from requests import Response
import requests
class SourceTestCase(ModuleTestCase):
'Test online source module'
module = 'investment'
@with_transaction()
def test_waitlist_source_request(self):
""" create source, call server
"""
pool = Pool()
OSource = pool.get('investment.source')
Asset = pool.get('investment.asset')
Product = pool.get('product.product')
company = self.prep_asset_company()
osource, = OSource.create([{
'name': 'Source 1',
'url': 'https://foo.bar/${isin}/${nsin}/${symbol}',
'rgxdate': 'Course Date (\\d+.\\d+.\\d+) Today',
'rgxdatefmt': '%d.%m.%Y',
'rgxrate': 'High (\\d+,\\d+) EUR',
'rgxdecimal': ',',
}])
self.assertEqual(osource.rec_name, 'Source 1')
product = self.prep_asset_product(
name='Product 1',
description='some asset')
Product.write(*[
[product],
{
'identifiers': [('create', [{
'type': 'wkn',
'code': '965515',
}, {
'type': 'secsymb',
'code': '1472977',
}, {
'type': 'isin',
'code': 'XC0009655157',
}, ])],
}])
asset = self.prep_asset_item(
company=company,
product = product)
Asset.write(*[
[asset],
{
'updtsources': [('add', [osource.id])],
}])
with Transaction().set_context({
'qdate': date(2022, 10, 1), # saturday
'qdatetime': datetime(2022, 10, 2, 10, 0, 0),
}):
asset2, = Asset.browse([asset])
self.assertEqual(asset2.wkn, '965515')
self.assertEqual(asset2.isin, 'XC0009655157')
self.assertEqual(asset2.secsymb, '1472977')
self.assertEqual(asset2.updttime, time(14, 0))
self.assertEqual(len(asset2.updtsources), 1)
self.assertEqual(asset2.updtsources[0].rec_name, 'Source 1')
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 = """<html><body>Response from finance-server
Course Date 14.08.2022 Today
High 34,87 EUR
</body></html>""".encode('utf8')
resp1.status_code = 200
resp1.reason = 'OK'
requests.get = MagicMock(return_value=resp1)
OSource.update_rate(asset)
self.assertEqual(len(asset.rates), 1)
self.assertEqual(asset.rates[0].date, date(2022, 8, 14))
self.assertEqual(asset.rates[0].rate, Decimal('34.87'))
@with_transaction()
def test_waitlist_source_check_regex(self):
""" create source, check convert
"""
pool = Pool()
OSource = pool.get('investment.source')
osource, = OSource.create([{
'name': 'Source 1',
'rgxdate': 'Course Date (\\d+.\\d+.\\d+) Today',
'rgxdatefmt': '%d.%m.%Y',
'rgxrate': 'High (\\d+,\\d+) EUR',
'rgxdecimal': ',',
}])
self.assertEqual(osource.rec_name, 'Source 1')
self.assertEqual(osource.get_regex_result(
'The Course Date 14.03.2022 Today, High 13,43 EUR',
'rgxdate'
), date(2022, 3, 14))
self.assertEqual(osource.get_regex_result(
'The Course Date 14.03.2022 Today, High 13,43 EUR',
'rgxrate'
), Decimal('13.43'))
# iso-date
OSource.write(*[
[osource],
{
'rgxdate': 'Course Date (\\d+-\\d+-\\d+) Today',
'rgxdatefmt': '%Y-%m-%d',
}])
self.assertEqual(osource.get_regex_result(
'The Course Date 2022-03-14 Today, High 13,43 EUR',
'rgxdate'
), date(2022, 3, 14))
# end SourceTestCase

View file

@ -1,78 +0,0 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction
from decimal import Decimal
from datetime import date
class WizardTestCase(ModuleTestCase):
'Test wizard module'
module = 'investment'
@with_transaction()
def test_wiz_run_import(self):
""" run import wizard
"""
pool = Pool()
Asset = pool.get('investment.asset')
ImportWiz = pool.get('investment.imp_wiz', type='wizard')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(len(asset.rates), 0)
with Transaction().set_context({
'active_id': asset.id,
'active_model': 'investment.asset',
}):
(sess_id, start_state, end_state) = ImportWiz.create()
w_obj = ImportWiz(sess_id)
self.assertEqual(start_state, 'start')
self.assertEqual(end_state, 'end')
# run start
result = ImportWiz.execute(sess_id, {}, start_state)
self.assertEqual(list(result.keys()), ['view'])
self.assertEqual(result['view']['defaults']['asset'], asset.id)
self.assertEqual(result['view']['defaults']['dec_divider'], ',')
self.assertEqual(result['view']['defaults']['date_fmt'], '%d.%m.%Y')
self.assertEqual(result['view']['defaults']['field_delimiter'], ';')
w_obj.start.asset = asset
w_obj.start.dec_divider = ','
w_obj.start.date_fmt = '%d.%m.%Y'
w_obj.start.field_delimiter = ';'
result = ImportWiz.execute(sess_id, {'start': {
'asset': asset.id,
'dec_divider': ',',
'date_fmt': '%d.%m.%Y',
'field_delimiter': ';',
'file_': b'"date";"rate"\n"03.05.2022";"23,56"\n"05.05.2022";"24,22"\n"06.05.2022";"25,43"',
}}, 'importf')
self.assertEqual(list(result.keys()), [])
# finish wizard
ImportWiz.delete(sess_id)
self.assertEqual(len(asset.rates), 3)
self.assertEqual(asset.rates[0].date, date(2022, 5, 6))
self.assertEqual(asset.rates[0].rate, Decimal('25.43'))
self.assertEqual(asset.rates[1].date, date(2022, 5, 5))
self.assertEqual(asset.rates[1].rate, Decimal('24.22'))
self.assertEqual(asset.rates[2].date, date(2022, 5, 3))
self.assertEqual(asset.rates[2].rate, Decimal('23.56'))
# end WizardTestCase

78
tests/wizard.py Normal file
View file

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-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 decimal import Decimal
from datetime import date
class WizardTestCase(object):
""" test import wizard
"""
@with_transaction()
def test_wiz_run_import(self):
""" run import wizard
"""
pool = Pool()
ImportWiz = pool.get('investment.imp_wiz', type='wizard')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(company=company, product=product)
self.assertEqual(len(asset.rates), 0)
with Transaction().set_context({
'active_id': asset.id,
'active_model': 'investment.asset'}):
(sess_id, start_state, end_state) = ImportWiz.create()
w_obj = ImportWiz(sess_id)
self.assertEqual(start_state, 'start')
self.assertEqual(end_state, 'end')
# run start
result = ImportWiz.execute(sess_id, {}, start_state)
self.assertEqual(list(result.keys()), ['view'])
self.assertEqual(result['view']['defaults']['asset'], asset.id)
self.assertEqual(result['view']['defaults']['dec_divider'], ',')
self.assertEqual(
result['view']['defaults']['date_fmt'],
'%d.%m.%Y')
self.assertEqual(
result['view']['defaults']['field_delimiter'],
';')
w_obj.start.asset = asset
w_obj.start.dec_divider = ','
w_obj.start.date_fmt = '%d.%m.%Y'
w_obj.start.field_delimiter = ';'
result = ImportWiz.execute(sess_id, {'start': {
'asset': asset.id,
'dec_divider': ',',
'date_fmt': '%d.%m.%Y',
'field_delimiter': ';',
'file_': b'"date";"rate"\n"03.05.2022";"23,56"\n' +
b'"05.05.2022";"24,22"\n"06.05.2022";"25,43"',
}}, 'importf')
self.assertEqual(list(result.keys()), [])
# finish wizard
ImportWiz.delete(sess_id)
self.assertEqual(len(asset.rates), 3)
self.assertEqual(asset.rates[0].date, date(2022, 5, 6))
self.assertEqual(asset.rates[0].rate, Decimal('25.43'))
self.assertEqual(asset.rates[1].date, date(2022, 5, 5))
self.assertEqual(asset.rates[1].rate, Decimal('24.22'))
self.assertEqual(asset.rates[2].date, date(2022, 5, 3))
self.assertEqual(asset.rates[2].rate, Decimal('23.56'))
# end WizardTestCase

View file

@ -1,5 +1,5 @@
[tryton]
version=6.0.23
version=7.0.0
depends:
ir
res

View file

@ -29,10 +29,9 @@ class UpdateSoureWizard(Wizard):
if OnlineSource.update_rate(asset):
to_run_activities.append(asset)
if len(to_run_activities) > 0:
if to_run_activities:
Asset.after_update_actions(to_run_activities)
return 'end'
# UpdateSoureWizard

View file

@ -1 +1 @@
diagram;6.0.7;6.0.999;mds

View file

@ -60,6 +60,11 @@ full copyright notices and license terms. -->
<label name="updtdays"/>
<field name="updtdays"/>
<label name="updturl"/>
<field name="updturl" colspan="3"/>
<field name="updturl_enable"/>
<newline/>
<field name="updtsources" colspan="4"/>
</page>
</notebook>

View file

@ -4,12 +4,12 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<tree>
<field name="name" expand="1"/>
<field name="change_day1" symbol="change_symbol"/>
<field name="change_month1" symbol="change_symbol"/>
<field name="change_month3" symbol="change_symbol"/>
<field name="change_month6" symbol="change_symbol"/>
<field name="date"/>
<field name="rate" symbol="asset_symbol"/>
<field name="isin"/>
<field name="wkn" />
<field name="change_day1" optional="0"/>
<field name="change_month1" optional="0"/>
<field name="change_month3" optional="0"/>
<field name="change_month6" optional="0"/>
<field name="date" optional="0"/>
<field name="rate" symbol="asset_symbol" optional="0"/>
<field name="isin" optional="0"/>
<field name="wkn" optional="0"/>
</tree>

View file

@ -1,14 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of the investment-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<data>
<xpath expr="/form/separator[@name='currency']" position="before">
<separator name="asset" colspan="6" string="Asset"/>
<label name="asset"/>
<field name="asset"/>
<newline/>
</xpath>
</data>

View file

@ -14,9 +14,12 @@ full copyright notices and license terms. -->
<field name="url" colspan="3"/>
<label name="nohtml"/>
<field name="nohtml"/>
<label id="labtp1" string=" "/>
<label xalign="0.0" colspan="3" name="nohtml"
string="URL parameter placeholders: ${isin}, ${nsin}, ${symbol}"/>
<label name="fixed_url"/>
<field name="fixed_url"/>
<newline/>
<separator colspan="6" name="url" string="Regular expressions to find data"/>