Compare commits
64 commits
ver_6.0.21
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4a3d996600 | ||
![]() |
ab6ab32d0a | ||
![]() |
4a2135512f | ||
![]() |
9f74b8fbf7 | ||
![]() |
c2df388692 | ||
![]() |
18ef1aaeb6 | ||
![]() |
faefea3b5f | ||
![]() |
aed948fb8a | ||
![]() |
ff5893b451 | ||
![]() |
6e77058946 | ||
![]() |
38e2c34a53 | ||
![]() |
3b9de6c0bb | ||
![]() |
ddffa302c4 | ||
![]() |
1c5b31d2f1 | ||
![]() |
765738d9ee | ||
![]() |
ebb6d71be3 | ||
![]() |
8f12741d76 | ||
![]() |
82f9d3a792 | ||
![]() |
edfa0424e5 | ||
![]() |
26192ea831 | ||
![]() |
fbbb7bd96c | ||
![]() |
6f1b3707bd | ||
![]() |
23665ae6dc | ||
![]() |
6789e63872 | ||
![]() |
40ef3ef192 | ||
![]() |
0daf39b913 | ||
![]() |
8503fd3e32 | ||
![]() |
8cfa54f693 | ||
![]() |
3dc79fc292 | ||
![]() |
8a4c8fa58f | ||
![]() |
166a9e13a9 | ||
![]() |
d9403e4946 | ||
![]() |
935be70112 | ||
![]() |
347f09b49e | ||
![]() |
77d6e5f8ef | ||
![]() |
94687139b1 | ||
![]() |
94169fb416 | ||
![]() |
ac0bced963 | ||
![]() |
fcabafa080 | ||
![]() |
e30953a55c | ||
![]() |
407569a684 | ||
![]() |
9061039ec2 | ||
![]() |
69afec62b0 | ||
![]() |
eb4af774c0 | ||
![]() |
e972d54f54 | ||
![]() |
23d4790e3b | ||
![]() |
71b52d106a | ||
![]() |
f4310c596a | ||
![]() |
de99f0e473 | ||
![]() |
c8c2afb190 | ||
![]() |
78fc16d7e2 | ||
![]() |
cc5a2fd164 | ||
![]() |
9c5af080f5 | ||
![]() |
258c14e2b5 | ||
![]() |
7334e53f9f | ||
![]() |
d08cf19b60 | ||
![]() |
1a61b112e0 | ||
![]() |
83acfbb14b | ||
![]() |
82c451377c | ||
![]() |
d70e674e58 | ||
![]() |
cba2117d13 | ||
![]() |
34faf91476 | ||
![]() |
f323334800 | ||
![]() |
1daa743631 |
32 changed files with 1902 additions and 1676 deletions
106
README.rst
106
README.rst
|
@ -9,103 +9,19 @@ pip install mds-investment
|
||||||
|
|
||||||
Requires
|
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
|
Changes
|
||||||
=======
|
=======
|
||||||
|
|
||||||
*6.0.21 - 08.01.2023*
|
*7.0.0 - 01.12.2023*
|
||||||
|
|
||||||
- add: caching for percentual changes
|
- compatibility to Tryton 7.0
|
||||||
|
|
||||||
*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
|
|
||||||
|
|
|
@ -32,4 +32,3 @@ def register():
|
||||||
UpdateSoureWizard,
|
UpdateSoureWizard,
|
||||||
ImportWizard,
|
ImportWizard,
|
||||||
module='investment', type_='wizard')
|
module='investment', type_='wizard')
|
||||||
|
|
||||||
|
|
441
asset.py
441
asset.py
|
@ -3,12 +3,11 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin
|
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin, Index
|
||||||
from trytond.transaction import Transaction
|
from trytond.transaction import Transaction
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from trytond.pyson import Eval, Bool, And, If, Date
|
from trytond.pyson import Eval, Bool, If, Date
|
||||||
from trytond.report import Report
|
from trytond.report import Report
|
||||||
from trytond.cache import Cache
|
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from datetime import time
|
from datetime import time
|
||||||
|
@ -16,6 +15,7 @@ from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
|
||||||
from sql.conditionals import Case, Coalesce, NullIf
|
from sql.conditionals import Case, Coalesce, NullIf
|
||||||
from sql import Literal
|
from sql import Literal
|
||||||
from .diagram import Concat2
|
from .diagram import Concat2
|
||||||
|
from .const import DEF_NONE
|
||||||
|
|
||||||
|
|
||||||
digits_percent = 2
|
digits_percent = 2
|
||||||
|
@ -30,98 +30,118 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
'Asset'
|
'Asset'
|
||||||
__name__ = 'investment.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')
|
'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")
|
required=True, ondelete="RESTRICT")
|
||||||
product = fields.Many2One(string='Product', required=True,
|
product = fields.Many2One(
|
||||||
model_name='product.product', ondelete='RESTRICT',
|
string='Product', required=True, model_name='product.product',
|
||||||
domain=[('type', '=', 'assets')])
|
ondelete='RESTRICT', domain=[('type', '=', 'assets')])
|
||||||
product_uom = fields.Function(fields.Many2One(string='UOM Category',
|
product_uom = fields.Function(fields.Many2One(
|
||||||
readonly=True, model_name='product.uom.category',
|
string='UOM Category', readonly=True,
|
||||||
|
model_name='product.uom.category',
|
||||||
help='Category of unit on the product.'), 'get_name_symbol')
|
help='Category of unit on the product.'), 'get_name_symbol')
|
||||||
uom = fields.Many2One(string='UOM', required=True,
|
uom = fields.Many2One(
|
||||||
model_name='product.uom', ondelete='RESTRICT',
|
string='UOM', required=True, model_name='product.uom',
|
||||||
states={
|
ondelete='RESTRICT',
|
||||||
'readonly': ~Bool(Eval('product')),
|
states={'readonly': ~Bool(Eval('product'))},
|
||||||
},
|
domain=[('category', '=', Eval('product_uom'))],
|
||||||
domain=[
|
depends=['product_uom', 'product'])
|
||||||
('category', '=', Eval('product_uom')),
|
symbol = fields.Function(fields.Char(
|
||||||
], depends=['product_uom', 'product'])
|
string='UOM', readonly=True), 'get_name_symbol',
|
||||||
symbol = fields.Function(fields.Char(string='UOM', readonly=True),
|
searcher='search_uom_symbol')
|
||||||
'get_name_symbol', searcher='search_uom_symbol')
|
asset_symbol = fields.Function(fields.Many2One(
|
||||||
asset_symbol = fields.Function(fields.Many2One(string='Symbol',
|
string='Symbol', readonly=True, model_name='investment.asset'),
|
||||||
readonly=True, model_name='investment.asset'),
|
|
||||||
'get_name_symbol')
|
'get_name_symbol')
|
||||||
|
|
||||||
rates = fields.One2Many(string='Rates', field='asset',
|
rates = fields.One2Many(
|
||||||
model_name='investment.rate')
|
string='Rates', field='asset', model_name='investment.rate')
|
||||||
rate = fields.Function(fields.Numeric(string='Current Rate',
|
rate = fields.Function(fields.Numeric(
|
||||||
readonly=True, digits=(16, Eval('currency_digits', 4)),
|
string='Current Rate', readonly=True,
|
||||||
depends=['currency_digits']),
|
digits=(16, Eval('currency_digits', 4)), depends=['currency_digits']),
|
||||||
'get_rate_data', searcher='search_rate')
|
'get_rate_data', searcher='search_rate')
|
||||||
date = fields.Function(fields.Date(string='Date', readonly=True,
|
date = fields.Function(fields.Date(
|
||||||
help='Date of current rate'),
|
string='Date', readonly=True, help='Date of current rate'),
|
||||||
'get_rate_data', searcher='search_date')
|
'get_rate_data', searcher='search_date')
|
||||||
|
|
||||||
currency = fields.Many2One(string='Currency', select=True,
|
currency = fields.Many2One(
|
||||||
required=True, model_name='currency.currency', ondelete='RESTRICT')
|
string='Currency', required=True,
|
||||||
currency_digits = fields.Integer(string='Digits', required=True,
|
model_name='currency.currency', ondelete='RESTRICT')
|
||||||
domain=[
|
currency_digits = fields.Integer(
|
||||||
('currency_digits', '>=', 0),
|
string='Digits', required=True,
|
||||||
('currency_digits', '<=', 6)])
|
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'),
|
help='National Securities Identifying Number'),
|
||||||
'get_identifiers', searcher='search_identifier')
|
'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'),
|
help='International Securities Identification Number'),
|
||||||
'get_identifiers', searcher='search_identifier')
|
'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'),
|
help='Stock market symbol'),
|
||||||
'get_identifiers', searcher='search_identifier')
|
'get_identifiers', searcher='search_identifier')
|
||||||
|
|
||||||
updtsources = fields.Many2Many(string='Update Sources',
|
updtsources = fields.Many2Many(
|
||||||
help='Select sources for the course update. The course sources are tried until a valid value has been read.',
|
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',
|
relation_name='investment.asset_source_rel',
|
||||||
origin='asset', target='source')
|
origin='asset', target='source')
|
||||||
updtdays = fields.Selection(string='Select days', required=True,
|
updturl = fields.Char(
|
||||||
selection=sel_updtdays)
|
string='URL',
|
||||||
updttime = fields.Time(string='Time',
|
help='URL for data retrieval.',
|
||||||
states={
|
states={
|
||||||
'readonly': ~Bool(Eval('updtsources')),
|
'invisible': ~Eval('updturl_enable', False),
|
||||||
}, depends=['updtsources'])
|
'required': Eval('updturl_enable', False),
|
||||||
nextupdate = fields.Function(fields.DateTime(string='Next Update',
|
}, depends=['updturl_enable'])
|
||||||
readonly=True),
|
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')
|
'get_nextupdates', searcher='search_nextupdate')
|
||||||
|
|
||||||
# percentage change
|
# percentage change
|
||||||
change_day1 = fields.Function(fields.Numeric(string='Previous Day',
|
change_day1 = fields.Function(fields.Numeric(
|
||||||
help='percentage change in value compared to the previous day',
|
string='Previous Day', readonly=True,
|
||||||
readonly=True, digits=(16,digits_percent)),
|
digits=(16, digits_percent)),
|
||||||
'get_percentage_change', searcher='search_percentage')
|
'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',
|
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')
|
'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',
|
help='percentage change in value during 3 months',
|
||||||
readonly=True, digits=(16,digits_percent)),
|
digits=(16, digits_percent)),
|
||||||
'get_percentage_change', searcher='search_percentage')
|
'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',
|
help='percentage change in value during 6 months',
|
||||||
readonly=True, digits=(16,digits_percent)),
|
digits=(16, digits_percent)),
|
||||||
'get_percentage_change', searcher='search_percentage')
|
'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',
|
help='percentage change in value during 1 year',
|
||||||
readonly=True, digits=(16,digits_percent)),
|
digits=(16, digits_percent)),
|
||||||
'get_percentage_change', searcher='search_percentage')
|
'get_percentage_change', searcher='search_percentage')
|
||||||
change_symbol = fields.Function(fields.Many2One(string='Symbol',
|
change_symbol = fields.Function(fields.Many2One(
|
||||||
readonly=True, model_name='investment.rate'),
|
string='Symbol', readonly=True, model_name='investment.rate'),
|
||||||
'get_rate_data')
|
'get_rate_data')
|
||||||
|
|
||||||
_asset_cache = Cache('investment.asset.values', duration=6*3600, context=False)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __register__(cls, module_name):
|
def __register__(cls, module_name):
|
||||||
""" register and migrate
|
""" register and migrate
|
||||||
|
@ -134,6 +154,21 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
super(Asset, cls).__setup__()
|
super(Asset, cls).__setup__()
|
||||||
cls._order.insert(0, ('name', 'ASC'))
|
cls._order.insert(0, ('name', 'ASC'))
|
||||||
cls._order.insert(0, ('date', 'DESC'))
|
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
|
@classmethod
|
||||||
def migrate_updtsource(cls, module_name):
|
def migrate_updtsource(cls, module_name):
|
||||||
|
@ -151,7 +186,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.id,
|
tab_asset.id,
|
||||||
tab_asset.updtsource,
|
tab_asset.updtsource,
|
||||||
where = tab_asset.updtsource != None,
|
where=tab_asset.updtsource != DEF_NONE,
|
||||||
)
|
)
|
||||||
cursor.execute(*query)
|
cursor.execute(*query)
|
||||||
records = cursor.fetchall()
|
records = cursor.fetchall()
|
||||||
|
@ -160,30 +195,17 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
'source': x[1],
|
'source': x[1],
|
||||||
} for x in records]
|
} for x in records]
|
||||||
|
|
||||||
if len(to_create) > 0:
|
if to_create:
|
||||||
AssetSourceRel.create(to_create)
|
AssetSourceRel.create(to_create)
|
||||||
asset_table.drop_column('updtsource')
|
asset_table.drop_column('updtsource')
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def values_cache_clear(cls, id_assets, names):
|
|
||||||
""" set cache-values to None
|
|
||||||
"""
|
|
||||||
for fname in names:
|
|
||||||
for id1 in id_assets:
|
|
||||||
cls._asset_cache.set('%(fname)s-%(id)d' % {
|
|
||||||
'fname': fname,
|
|
||||||
'id': id1,
|
|
||||||
}, None)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def view_attributes(cls):
|
def view_attributes(cls):
|
||||||
return super().view_attributes() + [
|
return super().view_attributes() + [
|
||||||
('/tree', 'visual',
|
('/tree', 'visual',
|
||||||
If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
|
If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
|
||||||
If(Eval('change_day1', 0) < 0, 'danger',
|
If(Eval('change_day1', 0) < 0, 'danger',
|
||||||
If(Eval('change_day1', 0) > 0, 'success', '')
|
If(Eval('change_day1', 0) > 0, 'success', '')))),
|
||||||
))
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -220,11 +242,21 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
return 'work'
|
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')
|
@fields.depends('updtsources', 'updttime')
|
||||||
def on_change_updtsources(self):
|
def on_change_updtsources(self):
|
||||||
""" clear time-fields
|
""" clear time-fields
|
||||||
"""
|
"""
|
||||||
if len(self.updtsources) == 0:
|
if not self.updtsources:
|
||||||
self.updttime = None
|
self.updttime = None
|
||||||
else:
|
else:
|
||||||
self.updttime = time(11, 30)
|
self.updttime = time(11, 30)
|
||||||
|
@ -264,15 +296,20 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
(tab1, join1, col1) = Uom.symbol._get_translation_column(Uom, 'symbol')
|
(tab1, join1, col1) = Uom.symbol._get_translation_column(Uom, 'symbol')
|
||||||
tab_symb = join1.select(tab1.id, col1.as_('symbol'))
|
tab_symb = join1.select(tab1.id, col1.as_('symbol'))
|
||||||
|
|
||||||
query = tab_asset.join(tab_prod,
|
query = tab_asset.join(
|
||||||
|
tab_prod,
|
||||||
condition=tab_asset.product == tab_prod.id,
|
condition=tab_asset.product == tab_prod.id,
|
||||||
).join(tab_templ,
|
).join(
|
||||||
|
tab_templ,
|
||||||
condition=tab_templ.id == tab_prod.template,
|
condition=tab_templ.id == tab_prod.template,
|
||||||
).join(tab_uom,
|
).join(
|
||||||
|
tab_uom,
|
||||||
condition=tab_templ.default_uom == tab_uom.id,
|
condition=tab_templ.default_uom == tab_uom.id,
|
||||||
).join(tab_cur,
|
).join(
|
||||||
|
tab_cur,
|
||||||
condition=tab_asset.currency == tab_cur.id,
|
condition=tab_asset.currency == tab_cur.id,
|
||||||
).join(tab_symb,
|
).join(
|
||||||
|
tab_symb,
|
||||||
condition=tab_asset.uom == tab_symb.id,
|
condition=tab_asset.uom == tab_symb.id,
|
||||||
).select(
|
).select(
|
||||||
tab_asset.id,
|
tab_asset.id,
|
||||||
|
@ -288,12 +325,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
cursor = Transaction().connection.cursor()
|
cursor = Transaction().connection.cursor()
|
||||||
|
|
||||||
(query, tab_asset) = cls.get_name_symbol_sql()
|
result = {x: {y.id: None for y in assets} for x in names}
|
||||||
query.where=tab_asset.id.in_([x.id for x in assets])
|
|
||||||
|
|
||||||
|
(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)
|
cursor.execute(*query)
|
||||||
records = cursor.fetchall()
|
records = cursor.fetchall()
|
||||||
result = {x:{y.id: None for y in assets} for x in names}
|
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
values = {
|
values = {
|
||||||
|
@ -313,8 +351,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
return ['OR',
|
return ['OR',
|
||||||
(('uom.rec_name',) + tuple(clause[1:])),
|
(('uom.rec_name',) + tuple(clause[1:])),
|
||||||
(('currency.rec_name',) + tuple(clause[1:])),
|
(('currency.rec_name',) + tuple(clause[1:]))]
|
||||||
]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_rate_data_sql(cls):
|
def get_rate_data_sql(cls):
|
||||||
|
@ -326,7 +363,8 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_asset = Asset.__table__()
|
tab_asset = Asset.__table__()
|
||||||
tab_rate = Rate.__table__()
|
tab_rate = Rate.__table__()
|
||||||
|
|
||||||
query = tab_asset.join(tab_rate,
|
query = tab_asset.join(
|
||||||
|
tab_rate,
|
||||||
condition=tab_asset.id == tab_rate.asset
|
condition=tab_asset.id == tab_rate.asset
|
||||||
).select(
|
).select(
|
||||||
tab_asset.id,
|
tab_asset.id,
|
||||||
|
@ -342,22 +380,23 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
def get_rate_data(cls, assets, names):
|
def get_rate_data(cls, assets, names):
|
||||||
""" get date and rate of asset
|
""" get date and rate of asset
|
||||||
"""
|
"""
|
||||||
Asset2 = Pool().get('investment.asset')
|
|
||||||
|
|
||||||
cursor = Transaction().connection.cursor()
|
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, 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)
|
cursor.execute(*query)
|
||||||
records = cursor.fetchall()
|
records = cursor.fetchall()
|
||||||
|
|
||||||
result = {x:{y.id: None for y in assets} for x in names}
|
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
(id1, rate1, date1, id_rate) = record
|
(id1, rate1, date1, id_rate) = record
|
||||||
|
|
||||||
asset = Asset2(id1)
|
curr_dig = curr_digits.get(id1, 4)
|
||||||
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
|
exp = Decimal(Decimal(1) / 10 ** curr_dig)
|
||||||
|
|
||||||
values = {
|
values = {
|
||||||
'rate': record[1].quantize(exp),
|
'rate': record[1].quantize(exp),
|
||||||
|
@ -367,7 +406,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
for name in names:
|
for name in names:
|
||||||
result[name][record[0]] = values[name]
|
result[name][record[0]] = values[name]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -379,8 +417,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_query.select(
|
query = tab_query.select(
|
||||||
tab_query.id,
|
tab_query.id,
|
||||||
where=Operator(tab_query.date, clause[2]),
|
where=Operator(tab_query.date, clause[2]))
|
||||||
)
|
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -392,8 +429,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_query.select(
|
query = tab_query.select(
|
||||||
tab_query.id,
|
tab_query.id,
|
||||||
where=Operator(tab_query.rate, clause[2]),
|
where=Operator(tab_query.rate, clause[2]))
|
||||||
)
|
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -405,8 +441,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_query.select(
|
query = tab_query.select(
|
||||||
tab_query.date,
|
tab_query.date,
|
||||||
where=tab_query.id==table.id,
|
where=tab_query.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -418,8 +453,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_query.select(
|
query = tab_query.select(
|
||||||
tab_query.rate,
|
tab_query.rate,
|
||||||
where=tab_query.id==table.id,
|
where=tab_query.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -430,8 +464,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
Rate = pool.get('investment.rate')
|
Rate = pool.get('investment.rate')
|
||||||
Asset2 = pool.get('investment.asset')
|
|
||||||
tab_asset = Asset2.__table__()
|
|
||||||
tab_rate1 = Rate.__table__()
|
tab_rate1 = Rate.__table__()
|
||||||
tab_rate2 = Rate.__table__()
|
tab_rate2 = Rate.__table__()
|
||||||
context = Transaction().context
|
context = Transaction().context
|
||||||
|
@ -439,103 +471,96 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
query_date = context.get('qdate', CurrentDate())
|
query_date = context.get('qdate', CurrentDate())
|
||||||
where_asset = tab_rate1.date <= query_date
|
where_asset = tab_rate1.date <= query_date
|
||||||
if isinstance(asset_ids, list):
|
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,
|
tab_today = tab_rate1.select(
|
||||||
condition=tab_asset.id==tab_rate1.asset,
|
tab_rate1.asset.as_('id'),
|
||||||
).select(
|
|
||||||
tab_asset.id,
|
|
||||||
tab_rate1.date,
|
tab_rate1.date,
|
||||||
tab_rate1.rate,
|
tab_rate1.rate,
|
||||||
distinct_on=[tab_asset.id],
|
distinct_on=[tab_rate1.asset],
|
||||||
order_by=[tab_asset.id, tab_rate1.date.desc],
|
order_by=[tab_rate1.asset, tab_rate1.date.desc],
|
||||||
where=where_asset,
|
where=where_asset)
|
||||||
)
|
|
||||||
|
|
||||||
days_diff = days + 5
|
days_diff = days + 5
|
||||||
query = tab_today.join(tab_rate2,
|
query = tab_today.join(
|
||||||
condition=(tab_today.id==tab_rate2.asset) & \
|
tab_rate2,
|
||||||
(tab_today.date > (tab_rate2.date + days)) & \
|
condition=(tab_today.id == tab_rate2.asset) &
|
||||||
|
(tab_today.date > (tab_rate2.date + days)) &
|
||||||
(tab_today.date <= (tab_rate2.date + days_diff)),
|
(tab_today.date <= (tab_rate2.date + days_diff)),
|
||||||
type_='LEFT OUTER',
|
type_='LEFT OUTER',
|
||||||
).select(
|
).select(
|
||||||
tab_today.id,
|
tab_today.id,
|
||||||
tab_today.date,
|
tab_today.date,
|
||||||
tab_today.rate,
|
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],
|
distinct_on=[tab_today.id],
|
||||||
order_by=[tab_today.id, tab_today.date.desc]
|
order_by=[tab_today.id, tab_rate2.date.desc])
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_change_day1(tables):
|
def order_change_day1(tables):
|
||||||
""" order day1
|
""" order day1
|
||||||
"""
|
"""
|
||||||
Assert = Pool().get('investment.asset')
|
Asset2 = Pool().get('investment.asset')
|
||||||
tab_asset = Asset.get_percentage_sql(days=0)
|
tab_asset = Asset2.get_percentage_sql(days=0)
|
||||||
table, _ = tables[None]
|
table, _ = tables[None]
|
||||||
|
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.percent,
|
tab_asset.percent,
|
||||||
where=tab_asset.id==table.id,
|
where=tab_asset.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_change_month1(tables):
|
def order_change_month1(tables):
|
||||||
""" order month1
|
""" order month1
|
||||||
"""
|
"""
|
||||||
Assert = Pool().get('investment.asset')
|
Asset2 = Pool().get('investment.asset')
|
||||||
tab_asset = Asset.get_percentage_sql(days=30)
|
tab_asset = Asset2.get_percentage_sql(days=30)
|
||||||
table, _ = tables[None]
|
table, _ = tables[None]
|
||||||
|
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.percent,
|
tab_asset.percent,
|
||||||
where=tab_asset.id==table.id,
|
where=tab_asset.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_change_month3(tables):
|
def order_change_month3(tables):
|
||||||
""" order month1
|
""" order month1
|
||||||
"""
|
"""
|
||||||
Assert = Pool().get('investment.asset')
|
Asset2 = Pool().get('investment.asset')
|
||||||
tab_asset = Asset.get_percentage_sql(days=90)
|
tab_asset = Asset2.get_percentage_sql(days=90)
|
||||||
table, _ = tables[None]
|
table, _ = tables[None]
|
||||||
|
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.percent,
|
tab_asset.percent,
|
||||||
where=tab_asset.id==table.id,
|
where=tab_asset.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_change_month6(tables):
|
def order_change_month6(tables):
|
||||||
""" order month1
|
""" order month1
|
||||||
"""
|
"""
|
||||||
Assert = Pool().get('investment.asset')
|
Asset2 = Pool().get('investment.asset')
|
||||||
tab_asset = Asset.get_percentage_sql(days=180)
|
tab_asset = Asset2.get_percentage_sql(days=180)
|
||||||
table, _ = tables[None]
|
table, _ = tables[None]
|
||||||
|
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.percent,
|
tab_asset.percent,
|
||||||
where=tab_asset.id==table.id,
|
where=tab_asset.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def order_change_month12(tables):
|
def order_change_month12(tables):
|
||||||
""" order month1
|
""" order month1
|
||||||
"""
|
"""
|
||||||
Assert = Pool().get('investment.asset')
|
Asset2 = Pool().get('investment.asset')
|
||||||
tab_asset = Asset.get_percentage_sql(days=365)
|
tab_asset = Asset2.get_percentage_sql(days=365)
|
||||||
table, _ = tables[None]
|
table, _ = tables[None]
|
||||||
|
|
||||||
query = tab_asset.select(
|
query = tab_asset.select(
|
||||||
tab_asset.percent,
|
tab_asset.percent,
|
||||||
where=tab_asset.id==table.id,
|
where=tab_asset.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -552,9 +577,9 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
'month12': 365,
|
'month12': 365,
|
||||||
}[field_name])
|
}[field_name])
|
||||||
|
|
||||||
query = tab_percent.select(tab_percent.id,
|
query = tab_percent.select(
|
||||||
where=Operator(Round(tab_percent.percent, 2),
|
tab_percent.id,
|
||||||
clause[2]))
|
where=Operator(Round(tab_percent.percent, 2), clause[2]))
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -567,22 +592,8 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
exp = Decimal(Decimal(1) / 10 ** digits_percent)
|
exp = Decimal(Decimal(1) / 10 ** digits_percent)
|
||||||
asset_id_lst = [x.id for x in assets]
|
asset_id_lst = [x.id for x in assets]
|
||||||
|
|
||||||
|
if asset_id_lst and names:
|
||||||
for x in names:
|
for x in names:
|
||||||
# try to get values from cache
|
|
||||||
ids_todo = []
|
|
||||||
for id1 in asset_id_lst:
|
|
||||||
c_val = cls._asset_cache.get('%(fname)s-%(id)d' % {
|
|
||||||
'fname': x,
|
|
||||||
'id': id1,
|
|
||||||
})
|
|
||||||
if c_val is None:
|
|
||||||
ids_todo.append(id1)
|
|
||||||
else :
|
|
||||||
result[x][id1] = c_val
|
|
||||||
|
|
||||||
if len(ids_todo) == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
tab_percent = cls.get_percentage_sql(
|
tab_percent = cls.get_percentage_sql(
|
||||||
days={
|
days={
|
||||||
'change_day1': 0,
|
'change_day1': 0,
|
||||||
|
@ -591,18 +602,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
'change_month6': 180,
|
'change_month6': 180,
|
||||||
'change_month12': 365,
|
'change_month12': 365,
|
||||||
}[x],
|
}[x],
|
||||||
asset_ids=ids_todo
|
asset_ids=asset_id_lst)
|
||||||
)
|
|
||||||
cursor.execute(*tab_percent)
|
cursor.execute(*tab_percent)
|
||||||
records = cursor.fetchall()
|
records = cursor.fetchall()
|
||||||
|
|
||||||
for record in records:
|
for record in records:
|
||||||
result[x][record[0]] = record[3].quantize(exp) \
|
result[x][record[0]] = record[3].quantize(exp) \
|
||||||
if record[3] is not None else None
|
if record[3] is not None else None
|
||||||
cls._asset_cache.set('%(fname)s-%(id)d' % {
|
|
||||||
'fname': x,
|
|
||||||
'id': record[0],
|
|
||||||
}, result[x][record[0]])
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -621,11 +627,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
query_date = context.get('qdate', CurrentDate() - Literal(1))
|
query_date = context.get('qdate', CurrentDate() - Literal(1))
|
||||||
|
|
||||||
# get last date of rate
|
# 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
|
# link to asset-source-relation to check if
|
||||||
# there are online-sources set
|
# there are online-sources set
|
||||||
condition=tab_rel.asset == tab_asset.id,
|
condition=tab_rel.asset == tab_asset.id,
|
||||||
).join(tab_rate,
|
).join(
|
||||||
|
tab_rate,
|
||||||
condition=tab_asset.id == tab_rate.asset,
|
condition=tab_asset.id == tab_rate.asset,
|
||||||
type_='LEFT OUTER',
|
type_='LEFT OUTER',
|
||||||
).select(
|
).select(
|
||||||
|
@ -634,19 +642,19 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_asset.updtdays,
|
tab_asset.updtdays,
|
||||||
tab_asset.updttime,
|
tab_asset.updttime,
|
||||||
distinct_on=[tab_asset.id],
|
distinct_on=[tab_asset.id],
|
||||||
order_by = [tab_asset.id, tab_rate.date.desc],
|
order_by=[tab_asset.id, tab_rate.date.desc])
|
||||||
)
|
|
||||||
|
|
||||||
query = tab_date.select(
|
query = tab_date.select(
|
||||||
tab_date.id,
|
tab_date.id,
|
||||||
(Case(
|
(Case(
|
||||||
((tab_date.updtdays == 'work') & \
|
((tab_date.updtdays == 'work') &
|
||||||
(Extract('dow', tab_date.date) == 0), tab_date.date + Literal(1)),
|
(Extract('dow', tab_date.date) == 0),
|
||||||
((tab_date.updtdays == 'work') & \
|
tab_date.date + Literal(1)),
|
||||||
(Extract('dow', tab_date.date) == 6), tab_date.date + Literal(2)),
|
((tab_date.updtdays == 'work') &
|
||||||
|
(Extract('dow', tab_date.date) == 6),
|
||||||
|
tab_date.date + Literal(2)),
|
||||||
else_=tab_date.date,
|
else_=tab_date.date,
|
||||||
) + tab_date.updttime).as_('updttime'),
|
) + tab_date.updttime).as_('updttime'))
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -660,8 +668,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
query = tab_updt.select(
|
query = tab_updt.select(
|
||||||
tab_updt.id,
|
tab_updt.id,
|
||||||
tab_updt.updttime,
|
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)
|
cursor.execute(*query)
|
||||||
records = cursor.fetchall()
|
records = cursor.fetchall()
|
||||||
|
|
||||||
|
@ -685,8 +692,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_updt.select(
|
query = tab_updt.select(
|
||||||
tab_updt.id,
|
tab_updt.id,
|
||||||
where=Operator(tab_updt.updttime, clause[2]),
|
where=Operator(tab_updt.updttime, clause[2]))
|
||||||
)
|
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -701,26 +707,29 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_secsymb = Identifier.__table__()
|
tab_secsymb = Identifier.__table__()
|
||||||
tab_isin = Identifier.__table__()
|
tab_isin = Identifier.__table__()
|
||||||
|
|
||||||
query = tab_asset.join(tab_prod,
|
query = tab_asset.join(
|
||||||
|
tab_prod,
|
||||||
condition=tab_asset.product == tab_prod.id,
|
condition=tab_asset.product == tab_prod.id,
|
||||||
).join(tab_wkn,
|
).join(
|
||||||
condition=(tab_prod.id==tab_wkn.product) & \
|
tab_wkn,
|
||||||
|
condition=(tab_prod.id == tab_wkn.product) &
|
||||||
(tab_wkn.type == 'wkn'),
|
(tab_wkn.type == 'wkn'),
|
||||||
type_='LEFT OUTER',
|
type_='LEFT OUTER',
|
||||||
).join(tab_secsymb,
|
).join(
|
||||||
condition=(tab_prod.id==tab_secsymb.product) & \
|
tab_secsymb,
|
||||||
|
condition=(tab_prod.id == tab_secsymb.product) &
|
||||||
(tab_secsymb.type == 'secsymb'),
|
(tab_secsymb.type == 'secsymb'),
|
||||||
type_='LEFT OUTER',
|
type_='LEFT OUTER',
|
||||||
).join(tab_isin,
|
).join(
|
||||||
condition=(tab_prod.id==tab_isin.product) & \
|
tab_isin,
|
||||||
|
condition=(tab_prod.id == tab_isin.product) &
|
||||||
(tab_isin.type == 'isin'),
|
(tab_isin.type == 'isin'),
|
||||||
type_='LEFT OUTER',
|
type_='LEFT OUTER',
|
||||||
).select(
|
).select(
|
||||||
tab_asset.id,
|
tab_asset.id,
|
||||||
tab_wkn.code.as_('wkn'),
|
tab_wkn.code.as_('wkn'),
|
||||||
tab_secsymb.code.as_('secsymb'),
|
tab_secsymb.code.as_('secsymb'),
|
||||||
tab_isin.code.as_('isin'),
|
tab_isin.code.as_('isin'))
|
||||||
)
|
|
||||||
return query
|
return query
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -736,13 +745,15 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_prod = Product.__table__()
|
tab_prod = Product.__table__()
|
||||||
tab_templ = Templ.__table__()
|
tab_templ = Templ.__table__()
|
||||||
|
|
||||||
query = tab_asset.join(tab_prod,
|
query = tab_asset.join(
|
||||||
|
tab_prod,
|
||||||
condition=tab_asset.product == tab_prod.id
|
condition=tab_asset.product == tab_prod.id
|
||||||
).join(tab_templ,
|
).join(
|
||||||
|
tab_templ,
|
||||||
condition=tab_templ.id == tab_prod.template
|
condition=tab_templ.id == tab_prod.template
|
||||||
).select(tab_templ.name,
|
).select(
|
||||||
where=tab_asset.id==table.id
|
tab_templ.name,
|
||||||
)
|
where=tab_asset.id == table.id)
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -755,8 +766,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_ids.select(
|
query = tab_ids.select(
|
||||||
getattr(tab_ids, 'wkn'),
|
getattr(tab_ids, 'wkn'),
|
||||||
where=tab_ids.id==table.id,
|
where=tab_ids.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -769,8 +779,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_ids.select(
|
query = tab_ids.select(
|
||||||
getattr(tab_ids, 'isin'),
|
getattr(tab_ids, 'isin'),
|
||||||
where=tab_ids.id==table.id,
|
where=tab_ids.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -783,8 +792,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
query = tab_ids.select(
|
query = tab_ids.select(
|
||||||
getattr(tab_ids, 'secsymb'),
|
getattr(tab_ids, 'secsymb'),
|
||||||
where=tab_ids.id==table.id,
|
where=tab_ids.id == table.id)
|
||||||
)
|
|
||||||
return [query]
|
return [query]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -798,13 +806,13 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_ids = cls.get_identifier_sql(tab_asset)
|
tab_ids = cls.get_identifier_sql(tab_asset)
|
||||||
|
|
||||||
field_qu = getattr(tab_ids, names)
|
field_qu = getattr(tab_ids, names)
|
||||||
query = tab_ids.join(tab_asset,
|
query = tab_ids.join(
|
||||||
|
tab_asset,
|
||||||
condition=tab_ids.id == tab_asset.id,
|
condition=tab_ids.id == tab_asset.id,
|
||||||
).select(
|
).select(
|
||||||
tab_asset.id,
|
tab_asset.id,
|
||||||
where=Operator(field_qu, clause[2]) & \
|
where=Operator(field_qu, clause[2]) &
|
||||||
(field_qu != None),
|
(field_qu != DEF_NONE))
|
||||||
)
|
|
||||||
|
|
||||||
return [('id', 'in', query)]
|
return [('id', 'in', query)]
|
||||||
|
|
||||||
|
@ -818,7 +826,7 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
cursor = Transaction().connection.cursor()
|
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 = cls.get_identifier_sql(tab_asset)
|
||||||
query.where = tab_asset.id.in_([x.id for x in assets])
|
query.where = tab_asset.id.in_([x.id for x in assets])
|
||||||
|
|
||||||
|
@ -831,7 +839,6 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
|
|
||||||
for n in names:
|
for n in names:
|
||||||
result[n][id1] = r1[n]
|
result[n][id1] = r1[n]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_rec_name(self, name):
|
def get_rec_name(self, name):
|
||||||
|
@ -840,17 +847,18 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
return '%(prod)s | %(rate)s %(unit)s | %(date)s' % {
|
return '%(prod)s | %(rate)s %(unit)s | %(date)s' % {
|
||||||
'prod': getattr(self.product, 'rec_name', '-'),
|
'prod': getattr(self.product, 'rec_name', '-'),
|
||||||
'unit': self.symbol,
|
'unit': self.symbol,
|
||||||
'rate': Report.format_number(self.rate, lang=None,
|
'rate': Report.format_number(
|
||||||
digits=self.currency_digits or 4) \
|
self.rate, lang=None, digits=self.currency_digits or 4)
|
||||||
if self.rate is not None else '-',
|
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
|
@classmethod
|
||||||
def search_rec_name(cls, name, clause):
|
def search_rec_name(cls, name, clause):
|
||||||
""" search in rec_name
|
""" search in rec_name
|
||||||
"""
|
"""
|
||||||
return ['OR',
|
return [
|
||||||
|
'OR',
|
||||||
('product.rec_name',) + tuple(clause[1:]),
|
('product.rec_name',) + tuple(clause[1:]),
|
||||||
('product.identifiers.code',) + tuple(clause[1:]),
|
('product.identifiers.code',) + tuple(clause[1:]),
|
||||||
]
|
]
|
||||||
|
@ -873,12 +881,11 @@ class Asset(SymbolMixin, ModelSQL, ModelView):
|
||||||
query_time = context.get('qdatetime', CurrentTimestamp())
|
query_time = context.get('qdatetime', CurrentTimestamp())
|
||||||
to_run_activities = []
|
to_run_activities = []
|
||||||
for asset in Asset2.search([
|
for asset in Asset2.search([
|
||||||
('nextupdate', '<=', query_time),
|
('nextupdate', '<=', query_time)]):
|
||||||
]):
|
|
||||||
if OnlineSource.update_rate(asset):
|
if OnlineSource.update_rate(asset):
|
||||||
to_run_activities.append(asset)
|
to_run_activities.append(asset)
|
||||||
|
|
||||||
if len(to_run_activities) > 0:
|
if to_run_activities:
|
||||||
cls.after_update_actions(to_run_activities)
|
cls.after_update_actions(to_run_activities)
|
||||||
|
|
||||||
# end Asset
|
# end Asset
|
||||||
|
@ -888,10 +895,12 @@ class AssetSourceRel(ModelSQL):
|
||||||
'Asset Source Relation'
|
'Asset Source Relation'
|
||||||
__name__ = 'investment.asset_source_rel'
|
__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',
|
required=True, model_name='investment.source',
|
||||||
ondelete='CASCADE')
|
ondelete='CASCADE')
|
||||||
asset = fields.Many2One(string='Asset', select=True,
|
asset = fields.Many2One(
|
||||||
|
string='Asset',
|
||||||
required=True, model_name='investment.asset',
|
required=True, model_name='investment.asset',
|
||||||
ondelete='CASCADE')
|
ondelete='CASCADE')
|
||||||
|
|
||||||
|
|
8
const.py
Normal file
8
const.py
Normal 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
|
23
diagram.py
23
diagram.py
|
@ -3,13 +3,11 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.model import ModelView, ModelSQL, fields
|
from trytond.model import fields
|
||||||
from trytond.transaction import Transaction
|
|
||||||
from trytond.pool import Pool, PoolMeta
|
from trytond.pool import Pool, PoolMeta
|
||||||
from trytond.pyson import Eval
|
from trytond.pyson import Eval
|
||||||
from sql.functions import Function
|
from sql.functions import Function
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
|
|
||||||
class Concat2(Function):
|
class Concat2(Function):
|
||||||
|
@ -24,8 +22,8 @@ class Concat2(Function):
|
||||||
class GraphDef(metaclass=PoolMeta):
|
class GraphDef(metaclass=PoolMeta):
|
||||||
__name__ = 'diagram.graphdef'
|
__name__ = 'diagram.graphdef'
|
||||||
|
|
||||||
asset = fields.Many2One(string='Asset',
|
asset = fields.Many2One(
|
||||||
model_name='investment.asset',
|
string='Asset', model_name='investment.asset',
|
||||||
states={
|
states={
|
||||||
'invisible': Eval('dtype', '') != 'investment.asset',
|
'invisible': Eval('dtype', '') != 'investment.asset',
|
||||||
'required': Eval('dtype', '') == 'investment.asset',
|
'required': Eval('dtype', '') == 'investment.asset',
|
||||||
|
@ -62,17 +60,18 @@ class GraphDef(metaclass=PoolMeta):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.scaling == 'alldata':
|
if self.scaling == 'alldata':
|
||||||
query = [('asset.id', '=', self.asset.id)]
|
query = [('asset', '=', self.asset.id)]
|
||||||
elif self.scaling == 'view':
|
elif self.scaling == 'view':
|
||||||
query = [
|
query = [
|
||||||
('asset.id', '=', self.asset.id),
|
('asset', '=', self.asset.id),
|
||||||
('date', '>=', self.chart.used_start_date()),
|
('date', '>=', self.chart.used_start_date()),
|
||||||
('date', '<=', self.chart.used_end_date()),
|
('date', '<=', self.chart.used_end_date()),
|
||||||
]
|
]
|
||||||
elif self.scaling == 'six':
|
elif self.scaling == 'six':
|
||||||
query = [
|
query = [
|
||||||
('asset.id', '=', self.asset.id),
|
('asset', '=', self.asset.id),
|
||||||
('date', '>=', self.chart.used_start_date() - timedelta(days=180)),
|
('date', '>=', self.chart.used_start_date() -
|
||||||
|
timedelta(days=180)),
|
||||||
('date', '<=', self.chart.used_end_date()),
|
('date', '<=', self.chart.used_end_date()),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -96,7 +95,7 @@ class ChartPoint(metaclass=PoolMeta):
|
||||||
"""
|
"""
|
||||||
Rate = Pool().get('investment.rate')
|
Rate = Pool().get('investment.rate')
|
||||||
|
|
||||||
if keyname is None:
|
if not keyname:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# check if query is for us
|
# check if query is for us
|
||||||
|
@ -105,12 +104,12 @@ class ChartPoint(metaclass=PoolMeta):
|
||||||
|
|
||||||
before = Rate.search([
|
before = Rate.search([
|
||||||
('date', '<', query_date),
|
('date', '<', query_date),
|
||||||
('asset.id', '=', asset_id),
|
('asset', '=', asset_id),
|
||||||
], limit=1, order=[('date', 'DESC')])
|
], limit=1, order=[('date', 'DESC')])
|
||||||
|
|
||||||
after = Rate.search([
|
after = Rate.search([
|
||||||
('date', '>', query_date),
|
('date', '>', query_date),
|
||||||
('asset.id', '=', asset_id),
|
('asset', '=', asset_id),
|
||||||
], limit=1, order=[('date', 'ASC')])
|
], limit=1, order=[('date', 'ASC')])
|
||||||
|
|
||||||
if (len(before) == 1) and (len(after) == 1):
|
if (len(before) == 1) and (len(after) == 1):
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.model import fields
|
|
||||||
from trytond.pool import PoolMeta
|
from trytond.pool import PoolMeta
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,15 +36,15 @@ class ImportWizardStart(ModelView):
|
||||||
'Import CSV-File'
|
'Import CSV-File'
|
||||||
__name__ = 'investment.imp_wiz.start'
|
__name__ = 'investment.imp_wiz.start'
|
||||||
|
|
||||||
asset = fields.Many2One(string='Asset', readonly=True,
|
asset = fields.Many2One(
|
||||||
model_name='investment.asset')
|
string='Asset', readonly=True, model_name='investment.asset')
|
||||||
file_ = fields.Binary(string="CSV-File", required=True)
|
file_ = fields.Binary(string="CSV-File", required=True)
|
||||||
dec_divider = fields.Selection(string='Decimal divider',
|
dec_divider = fields.Selection(
|
||||||
required=True, selection=sel_dec_divider)
|
string='Decimal divider', required=True, selection=sel_dec_divider)
|
||||||
date_fmt = fields.Selection(string='Date format',
|
date_fmt = fields.Selection(
|
||||||
required=True, selection=sel_date_fmt)
|
string='Date format', required=True, selection=sel_date_fmt)
|
||||||
field_delimiter = fields.Selection(string='Field delimiter',
|
field_delimiter = fields.Selection(
|
||||||
required=True, selection=sel_field_delimiter)
|
string='Field delimiter', required=True, selection=sel_field_delimiter)
|
||||||
|
|
||||||
# end ImportWizardStart
|
# end ImportWizardStart
|
||||||
|
|
||||||
|
@ -54,11 +54,14 @@ class ImportWizard(Wizard):
|
||||||
__name__ = 'investment.imp_wiz'
|
__name__ = 'investment.imp_wiz'
|
||||||
|
|
||||||
start_state = 'start'
|
start_state = 'start'
|
||||||
start = StateView(model_name='investment.imp_wiz.start', \
|
start = StateView(
|
||||||
view='investment.imp_wiz_start_form', \
|
model_name='investment.imp_wiz.start',
|
||||||
|
view='investment.imp_wiz_start_form',
|
||||||
buttons=[
|
buttons=[
|
||||||
Button(string='Cancel', state='end', icon='tryton-cancel'),
|
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()
|
importf = StateTransition()
|
||||||
|
|
||||||
|
@ -81,14 +84,14 @@ class ImportWizard(Wizard):
|
||||||
pool = Pool()
|
pool = Pool()
|
||||||
ImportWiz = pool.get('investment.imp_wiz', type='wizard')
|
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(
|
(lines, max_date, min_date) = ImportWiz.read_csv_file(
|
||||||
self.start.file_.decode('utf8'),
|
self.start.file_.decode('utf8'),
|
||||||
dec_divider=self.start.dec_divider,
|
dec_divider=self.start.dec_divider,
|
||||||
date_fmt=self.start.date_fmt,
|
date_fmt=self.start.date_fmt,
|
||||||
delimiter=self.start.field_delimiter)
|
delimiter=self.start.field_delimiter)
|
||||||
|
|
||||||
if len(lines) > 0:
|
if lines:
|
||||||
ImportWiz.upload_rates(
|
ImportWiz.upload_rates(
|
||||||
self.start.asset,
|
self.start.asset,
|
||||||
lines, min_date, max_date)
|
lines, min_date, max_date)
|
||||||
|
@ -140,7 +143,8 @@ class ImportWizard(Wizard):
|
||||||
max_rate = None
|
max_rate = None
|
||||||
|
|
||||||
with StringIO(file_content) as fhdl:
|
with StringIO(file_content) as fhdl:
|
||||||
csv_lines = csv.DictReader(fhdl,
|
csv_lines = csv.DictReader(
|
||||||
|
fhdl,
|
||||||
fieldnames=['date', 'rate'],
|
fieldnames=['date', 'rate'],
|
||||||
dialect='excel',
|
dialect='excel',
|
||||||
delimiter=delimiter)
|
delimiter=delimiter)
|
||||||
|
@ -151,24 +155,26 @@ class ImportWizard(Wizard):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
date_val = datetime.strptime(line.get('date', None).strip(), date_fmt).date()
|
date_val = datetime.strptime(
|
||||||
except :
|
line.get('date', None).strip(), date_fmt).date()
|
||||||
|
except Exception:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'investment.msg_import_err_date',
|
'investment.msg_import_err_date',
|
||||||
datefmt=date_fmt,
|
datefmt=date_fmt,
|
||||||
colnr='1',
|
colnr='1',
|
||||||
))
|
))
|
||||||
try:
|
try:
|
||||||
rate_val = line.get('rate', None).replace(del_chars[0], '').strip()
|
rate_val = line.get('rate', None).replace(
|
||||||
|
del_chars[0], '').strip()
|
||||||
rate_val = Decimal(rate_val.replace(dec_divider, '.'))
|
rate_val = Decimal(rate_val.replace(dec_divider, '.'))
|
||||||
except :
|
except Exception:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'investment.msg_import_err_date',
|
'investment.msg_import_err_date',
|
||||||
datefmt='dd%sdd' % dec_divider,
|
datefmt='dd%sdd' % dec_divider,
|
||||||
colnr = '2',
|
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})
|
result.append({'date': date_val, 'rate': rate_val})
|
||||||
|
|
||||||
# date range
|
# date range
|
||||||
|
@ -199,9 +205,7 @@ class ImportWizard(Wizard):
|
||||||
else:
|
else:
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'investment.msg_err_unknown_content',
|
'investment.msg_err_unknown_content',
|
||||||
linetxt = line,
|
linetxt=line))
|
||||||
))
|
|
||||||
return (result, max_date, min_date)
|
return (result, max_date, min_date)
|
||||||
|
|
||||||
# end ImportWizard
|
# end ImportWizard
|
||||||
|
|
||||||
|
|
44
locale/de.po
44
locale/de.po
|
@ -42,6 +42,18 @@ msgctxt "model:ir.message,text:msg_err_unknown_content"
|
||||||
msgid "failed to identify row content: %(linetxt)s"
|
msgid "failed to identify row content: %(linetxt)s"
|
||||||
msgstr "Zeileninhalt konnte nicht identifiziert werden: %(linetxt)s"
|
msgstr "Zeileninhalt konnte nicht identifiziert werden: %(linetxt)s"
|
||||||
|
|
||||||
|
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 #
|
# ir.ui.menu #
|
||||||
|
@ -326,6 +338,18 @@ msgctxt "selection:investment.asset,updtdays:"
|
||||||
msgid "Mon - Sun"
|
msgid "Mon - Sun"
|
||||||
msgstr "Mo - So"
|
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 #
|
# investment.asset-source-rel #
|
||||||
|
@ -386,6 +410,10 @@ msgctxt "view:investment.source:"
|
||||||
msgid "Purely javascript-based websites do not work here."
|
msgid "Purely javascript-based websites do not work here."
|
||||||
msgstr "Rein javascriptbasierte Webseiten funktionieren hier nicht."
|
msgstr "Rein javascriptbasierte Webseiten funktionieren hier nicht."
|
||||||
|
|
||||||
|
msgctxt "view:investment.source:"
|
||||||
|
msgid "Method: Extract from web page"
|
||||||
|
msgstr "Methode: aus Webseite auslesen"
|
||||||
|
|
||||||
msgctxt "field:investment.source,name:"
|
msgctxt "field:investment.source,name:"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
@ -514,6 +542,22 @@ msgctxt "help:investment.source,fndident:"
|
||||||
msgid "Identifier found during test query."
|
msgid "Identifier found during test query."
|
||||||
msgstr "Bei der Testabfrage gefundener Bezeichner."
|
msgstr "Bei der Testabfrage gefundener Bezeichner."
|
||||||
|
|
||||||
|
msgctxt "field:investment.source,query_method:"
|
||||||
|
msgid "Method"
|
||||||
|
msgstr "Methode"
|
||||||
|
|
||||||
|
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 #
|
# investment.rate #
|
||||||
|
|
44
locale/en.po
44
locale/en.po
|
@ -30,6 +30,18 @@ msgctxt "model:ir.message,text:msg_err_unknown_content"
|
||||||
msgid "failed to identify row content: %(linetxt)s"
|
msgid "failed to identify row content: %(linetxt)s"
|
||||||
msgstr "failed to identify row content: %(linetxt)s"
|
msgstr "failed to identify row content: %(linetxt)s"
|
||||||
|
|
||||||
|
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"
|
msgctxt "model:ir.ui.menu,name:menu_investment"
|
||||||
msgid "Investment"
|
msgid "Investment"
|
||||||
msgstr "Investment"
|
msgstr "Investment"
|
||||||
|
@ -286,6 +298,18 @@ msgctxt "selection:investment.asset,updtdays:"
|
||||||
msgid "Mon - Sun"
|
msgid "Mon - Sun"
|
||||||
msgstr "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:"
|
msgctxt "model:investment.asset_source_rel,name:"
|
||||||
msgid "Asset Source Relation"
|
msgid "Asset Source Relation"
|
||||||
msgstr "Asset Source Relation"
|
msgstr "Asset Source Relation"
|
||||||
|
@ -338,6 +362,10 @@ msgctxt "view:investment.source:"
|
||||||
msgid "Purely javascript-based websites do not work here."
|
msgid "Purely javascript-based websites do not work here."
|
||||||
msgstr "Purely javascript-based websites do not work here."
|
msgstr "Purely javascript-based websites do not work here."
|
||||||
|
|
||||||
|
msgctxt "view:investment.source:"
|
||||||
|
msgid "Method: Extract from web page"
|
||||||
|
msgstr "Method: Extract from web page"
|
||||||
|
|
||||||
msgctxt "field:investment.source,name:"
|
msgctxt "field:investment.source,name:"
|
||||||
msgid "Name"
|
msgid "Name"
|
||||||
msgstr "Name"
|
msgstr "Name"
|
||||||
|
@ -466,6 +494,22 @@ msgctxt "help:investment.source,fndident:"
|
||||||
msgid "Identifier found during test query."
|
msgid "Identifier found during test query."
|
||||||
msgstr "Identifier found during test query."
|
msgstr "Identifier found during test query."
|
||||||
|
|
||||||
|
msgctxt "field:investment.source,query_method:"
|
||||||
|
msgid "Method"
|
||||||
|
msgstr "Method"
|
||||||
|
|
||||||
|
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:"
|
msgctxt "model:investment.rate,name:"
|
||||||
msgid "Rate"
|
msgid "Rate"
|
||||||
msgstr "Rate"
|
msgstr "Rate"
|
||||||
|
|
|
@ -17,6 +17,15 @@ full copyright notices and license terms. -->
|
||||||
<record model="ir.message" id="msg_err_unknown_content">
|
<record model="ir.message" id="msg_err_unknown_content">
|
||||||
<field name="text">failed to identify row content: %(linetxt)s</field>
|
<field name="text">failed to identify row content: %(linetxt)s</field>
|
||||||
</record>
|
</record>
|
||||||
|
<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>
|
</data>
|
||||||
</tryton>
|
</tryton>
|
||||||
|
|
231
onlinesource.py
231
onlinesource.py
|
@ -4,13 +4,17 @@
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from string import Template
|
from string import Template
|
||||||
import requests, logging, html2text, re
|
import requests
|
||||||
|
import logging
|
||||||
|
import html2text
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from trytond.model import ModelView, ModelSQL, fields, Unique, Check
|
from trytond.model import ModelView, ModelSQL, fields
|
||||||
from trytond.transaction import Transaction
|
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from trytond.pyson import Eval, Bool
|
from trytond.pyson import Eval, Bool
|
||||||
|
from trytond.i18n import gettext
|
||||||
|
from trytond.exceptions import UserError
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,56 +39,94 @@ sel_rgxdatefmt = [
|
||||||
('%b %d %Y', 'mon dd yyyy'),
|
('%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']
|
'fnddate', 'fndrate', 'fndident']
|
||||||
|
|
||||||
|
|
||||||
|
STATES_WEB = {
|
||||||
|
'invisible': Eval('query_method', '') != 'web',
|
||||||
|
'required': Eval('query_method', '') == 'web',
|
||||||
|
}
|
||||||
|
DEPENDS_WEB = ['query_method']
|
||||||
|
|
||||||
|
|
||||||
class OnlineSource(ModelSQL, ModelView):
|
class OnlineSource(ModelSQL, ModelView):
|
||||||
'Online Source'
|
'Online Source'
|
||||||
__name__ = 'investment.source'
|
__name__ = 'investment.source'
|
||||||
|
|
||||||
name = fields.Char(string='Name', required=True)
|
name = fields.Char(string='Name', required=True)
|
||||||
url = fields.Char(string='URL', required=True)
|
query_method = fields.Selection(
|
||||||
nohtml = fields.Boolean(string='Remove HTML',
|
string='Method', required=True,
|
||||||
help='Removes HTML tags before the text is interpreted.')
|
help='Select the method to retrieve the data.',
|
||||||
rgxdate = fields.Char(string='Date', required=True,
|
selection='get_query_methods')
|
||||||
help='Regex code to find the date in the downloaded HTML file.')
|
url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB)
|
||||||
rgxdatefmt = fields.Selection(string='Date format', required=True,
|
fixed_url = fields.Boolean(
|
||||||
selection=sel_rgxdatefmt)
|
string='Fixed URL',
|
||||||
rgxrate = fields.Char(string='Rate', required=True,
|
states={'invisible': Eval('query_method', '') != 'web'},
|
||||||
help='Regex code to find the rate in the downloaded HTML file.')
|
depends=DEPENDS_WEB,
|
||||||
rgxdecimal = fields.Selection(string='Decimal Separator', required=True,
|
help='URL must be defined at investment record.')
|
||||||
help='Decimal separator for converting the market value into a number.',
|
nohtml = fields.Boolean(
|
||||||
selection=sel_rgxdecimal)
|
string='Remove HTML',
|
||||||
rgxident = fields.Char(string='Identifier',
|
help='Removes HTML tags before the text is interpreted.',
|
||||||
help='Regex code to find the identifier in the downloaded HTML file.')
|
states={'invisible': STATES_WEB['invisible']},
|
||||||
rgxidtype = fields.Selection(string='ID-Type', selection=sel_rgxidtype,
|
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,
|
||||||
|
states=STATES_WEB, depends=DEPENDS_WEB)
|
||||||
|
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.',
|
||||||
|
selection=sel_rgxdecimal, states=STATES_WEB, depends=DEPENDS_WEB)
|
||||||
|
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,
|
||||||
help='Type of identifier used to validate the result.',
|
help='Type of identifier used to validate the result.',
|
||||||
states={
|
states={
|
||||||
'required': Bool(Eval('rgxident', '')),
|
'required': Bool(Eval('rgxident', '')),
|
||||||
}, depends=['rgxident'])
|
'invisible': STATES_WEB['invisible'],
|
||||||
|
}, depends=DEPENDS_WEB+['rgxident'])
|
||||||
|
|
||||||
# field to test requests
|
# field to test requests
|
||||||
used_url = fields.Function(fields.Char(string='Used URL', readonly=True,
|
used_url = fields.Function(fields.Char(
|
||||||
help='This URL is used to retrieve the HTML file.'),
|
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')
|
'on_change_with_used_url')
|
||||||
nsin = fields.Function(fields.Char(string='NSIN'),
|
nsin = fields.Function(fields.Char(
|
||||||
'on_change_with_nsin', setter='set_test_value')
|
string='NSIN'), 'on_change_with_nsin', setter='set_test_value')
|
||||||
isin = fields.Function(fields.Char(string='ISIN'),
|
isin = fields.Function(fields.Char(
|
||||||
'on_change_with_isin', setter='set_test_value')
|
string='ISIN'), 'on_change_with_isin', setter='set_test_value')
|
||||||
symbol = fields.Function(fields.Char(string='Symbol'),
|
symbol = fields.Function(fields.Char(
|
||||||
'on_change_with_symbol', setter='set_test_value')
|
string='Symbol'), 'on_change_with_symbol', setter='set_test_value')
|
||||||
http_state = fields.Function(fields.Char(string='HTTP-State',
|
http_state = fields.Function(fields.Char(
|
||||||
|
string='HTTP-State',
|
||||||
readonly=True), 'on_change_with_http_state')
|
readonly=True), 'on_change_with_http_state')
|
||||||
text = fields.Function(fields.Text(string='Result',
|
text = fields.Function(fields.Text(
|
||||||
readonly=True), 'on_change_with_text')
|
string='Result', readonly=True), 'on_change_with_text')
|
||||||
fnddate = fields.Function(fields.Date(string='Date', readonly=True,
|
fnddate = fields.Function(fields.Date(
|
||||||
|
string='Date', readonly=True,
|
||||||
help='Date found during test query.'),
|
help='Date found during test query.'),
|
||||||
'on_change_with_fnddate')
|
'on_change_with_fnddate')
|
||||||
fndrate = fields.Function(fields.Numeric(string='Rate', readonly=True,
|
fndrate = fields.Function(fields.Numeric(
|
||||||
|
string='Rate', readonly=True,
|
||||||
help='Rate found during test query.', digits=(16, 4)),
|
help='Rate found during test query.', digits=(16, 4)),
|
||||||
'on_change_with_fndrate')
|
'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.'),
|
help='Identifier found during test query.'),
|
||||||
'on_change_with_fndident')
|
'on_change_with_fndident')
|
||||||
|
|
||||||
|
@ -93,6 +135,12 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
super(OnlineSource, cls).__setup__()
|
super(OnlineSource, cls).__setup__()
|
||||||
cls._order.insert(0, ('name', 'DESC'))
|
cls._order.insert(0, ('name', 'DESC'))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_query_method(cls):
|
||||||
|
""" default: web
|
||||||
|
"""
|
||||||
|
return 'web'
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default_url(cls):
|
def default_url(cls):
|
||||||
""" defaul-url
|
""" defaul-url
|
||||||
|
@ -135,6 +183,12 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default_fixed_url(cls):
|
||||||
|
""" default: False
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@fields.depends(*fields_check)
|
@fields.depends(*fields_check)
|
||||||
def on_change_nsin(self):
|
def on_change_nsin(self):
|
||||||
""" run request
|
""" run request
|
||||||
|
@ -177,7 +231,7 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
def on_change_with_symbol(self, name=None):
|
def on_change_with_symbol(self, name=None):
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@fields.depends('url', 'isin', 'nsin', 'symbol')
|
@fields.depends('url', 'isin', 'nsin', 'symbol', 'fixed_url')
|
||||||
def on_change_with_used_url(self, name=None):
|
def on_change_with_used_url(self, name=None):
|
||||||
""" get url for testing
|
""" get url for testing
|
||||||
"""
|
"""
|
||||||
|
@ -186,7 +240,13 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
isin=self.isin,
|
isin=self.isin,
|
||||||
nsin=self.nsin,
|
nsin=self.nsin,
|
||||||
symbol=self.symbol,
|
symbol=self.symbol,
|
||||||
)
|
url=self.url)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_query_methods(cls):
|
||||||
|
""" get list of query-methods
|
||||||
|
"""
|
||||||
|
return [('web', gettext('investment.msg_querytype_web'))]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_test_value(cls, record, name, value):
|
def set_test_value(cls, record, name, value):
|
||||||
|
@ -194,34 +254,69 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
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,
|
||||||
|
'http_state': state of query,
|
||||||
|
'date': date() if success,
|
||||||
|
'rate': Decimal() if success,
|
||||||
|
'code': identifier - isin/nsin/symbol
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
OSourc = Pool().get('investment.source')
|
||||||
|
|
||||||
|
if getattr(osource, 'query_method', None) == 'web':
|
||||||
|
return OSourc.read_from_website(
|
||||||
|
osource,
|
||||||
|
isin=isin,
|
||||||
|
nsin=nsin,
|
||||||
|
symbol=symbol,
|
||||||
|
debug=debug,
|
||||||
|
url=url)
|
||||||
|
|
||||||
def call_online_source(self):
|
def call_online_source(self):
|
||||||
""" use updated values to call online-source,
|
""" use updated values to call online-source,
|
||||||
for testing parameters
|
for testing parameters
|
||||||
"""
|
"""
|
||||||
OSourc = Pool().get('investment.source')
|
OSourc = Pool().get('investment.source')
|
||||||
|
|
||||||
result = OSourc.read_from_website(
|
result = OSourc.run_query_method(
|
||||||
self,
|
self, self.isin, self.nsin, self.url,
|
||||||
isin = self.isin,
|
self.symbol, debug=True)
|
||||||
nsin = self.nsin,
|
if result:
|
||||||
symbol = self.symbol,
|
|
||||||
debug = True,
|
|
||||||
)
|
|
||||||
self.text = result.get('text', None)
|
self.text = result.get('text', None)
|
||||||
self.http_state = result.get('http_state', None)
|
self.http_state = result.get('http_state', None)
|
||||||
self.fnddate = result.get('date', None)
|
self.fnddate = result.get('date', None)
|
||||||
self.fndrate = result.get('rate', None)
|
self.fndrate = result.get('rate', None)
|
||||||
self.fndident = result.get('code', 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
|
""" 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:
|
if self.url:
|
||||||
return Template(self.url).substitute({
|
return Template(self.url).substitute({
|
||||||
'isin': isin if isin is not None else '',
|
'isin': isin if isin is not None else '',
|
||||||
'nsin': nsin if nsin 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
|
@classmethod
|
||||||
def update_rate(cls, asset):
|
def update_rate(cls, asset):
|
||||||
|
@ -235,12 +330,12 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
return
|
return
|
||||||
|
|
||||||
for updtsource in asset.updtsources:
|
for updtsource in asset.updtsources:
|
||||||
rate_data = cls.read_from_website(
|
rate_data = cls.run_query_method(
|
||||||
updtsource,
|
updtsource,
|
||||||
isin=asset.isin,
|
isin=asset.isin,
|
||||||
nsin=asset.wkn,
|
nsin=asset.wkn,
|
||||||
symbol=asset.secsymb,
|
symbol=asset.secsymb,
|
||||||
)
|
url=asset.updturl)
|
||||||
|
|
||||||
if len(updtsource.rgxident or '') > 0:
|
if len(updtsource.rgxident or '') > 0:
|
||||||
# check result - same code?
|
# check result - same code?
|
||||||
|
@ -254,24 +349,22 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
if (asset_code or '').lower() != code.lower():
|
if (asset_code or '').lower() != code.lower():
|
||||||
# fail
|
# fail
|
||||||
logger.warning(
|
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,
|
'exp': asset_code,
|
||||||
'wrong': code,
|
'wrong': code})
|
||||||
})
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
to_create = {
|
to_create = {
|
||||||
'date': rate_data.get('date', None),
|
'date': rate_data.get('date', None),
|
||||||
'rate': rate_data.get('rate', None),
|
'rate': rate_data.get('rate', None),
|
||||||
'asset': asset.id,
|
'asset': asset.id}
|
||||||
}
|
|
||||||
if (to_create['date'] is not None) and \
|
if (to_create['date'] is not None) and \
|
||||||
(to_create['rate'] is not None):
|
(to_create['rate'] is not None):
|
||||||
# check if exists
|
# check if exists
|
||||||
if Rate.search_count([
|
if Rate.search_count([
|
||||||
('asset.id', '=', asset.id),
|
('asset.id', '=', asset.id),
|
||||||
('date', '=', to_create['date']),
|
('date', '=', to_create['date'])]) == 0:
|
||||||
]) == 0:
|
|
||||||
Rate.create([to_create])
|
Rate.create([to_create])
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -284,13 +377,22 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
def get_regex_result(self, html_text, field_name):
|
def get_regex_result(self, html_text, field_name):
|
||||||
""" run regex on html-text, convert result
|
""" run regex on html-text, convert result
|
||||||
"""
|
"""
|
||||||
|
OSource = Pool().get('investment.source')
|
||||||
|
|
||||||
rgxcode = getattr(self, field_name) or ''
|
rgxcode = getattr(self, field_name) or ''
|
||||||
if len(rgxcode) == 0:
|
if len(rgxcode) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
search_result = re.compile(rgxcode).search(html_text)
|
search_result = re.compile(rgxcode).search(html_text)
|
||||||
if search_result is None:
|
if search_result is None:
|
||||||
return 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)
|
result = search_result.group(1)
|
||||||
|
@ -301,20 +403,23 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
dec_sep = [',', '.']
|
dec_sep = [',', '.']
|
||||||
dec_sep.remove(self.rgxdecimal)
|
dec_sep.remove(self.rgxdecimal)
|
||||||
|
|
||||||
result = result.replace(dec_sep[0], '').replace(self.rgxdecimal, '.')
|
result = result.replace(
|
||||||
|
dec_sep[0], '').replace(self.rgxdecimal, '.')
|
||||||
try:
|
try:
|
||||||
result = Decimal(result)
|
result = Decimal(result)
|
||||||
except :
|
except Exception:
|
||||||
result = None
|
result = None
|
||||||
elif field_name == 'rgxdate':
|
elif field_name == 'rgxdate':
|
||||||
try:
|
try:
|
||||||
result = datetime.strptime(result, self.rgxdatefmt).date()
|
result = datetime.strptime(result, self.rgxdatefmt).date()
|
||||||
except :
|
except Exception:
|
||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@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
|
""" read from url, extract values
|
||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
|
@ -328,7 +433,7 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
isin=isin,
|
isin=isin,
|
||||||
nsin=nsin,
|
nsin=nsin,
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
),
|
url=url),
|
||||||
allow_redirects=True,
|
allow_redirects=True,
|
||||||
timeout=5.0)
|
timeout=5.0)
|
||||||
|
|
||||||
|
@ -337,7 +442,7 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
'msg': res1.reason,
|
'msg': res1.reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
if res1.status_code in [200, 204]:
|
if res1.status_code in [200, 204, 410]:
|
||||||
html = res1.text
|
html = res1.text
|
||||||
|
|
||||||
# remove html-tags
|
# remove html-tags
|
||||||
|
@ -358,7 +463,9 @@ class OnlineSource(ModelSQL, ModelView):
|
||||||
result['date'] = updtsource.get_regex_result(html, 'rgxdate')
|
result['date'] = updtsource.get_regex_result(html, 'rgxdate')
|
||||||
result['code'] = updtsource.get_regex_result(html, 'rgxident')
|
result['code'] = updtsource.get_regex_result(html, 'rgxident')
|
||||||
else:
|
else:
|
||||||
logger.error('read_from_website: %(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
|
logger.error(
|
||||||
|
'read_from_website: ' +
|
||||||
|
'%(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
|
||||||
'code': res1.status_code,
|
'code': res1.status_code,
|
||||||
'url': res1.url,
|
'url': res1.url,
|
||||||
'redirects': ', '.join([x.url for x in res1.history]),
|
'redirects': ', '.join([x.url for x in res1.history]),
|
||||||
|
|
82
rate.py
82
rate.py
|
@ -3,33 +3,35 @@
|
||||||
# The COPYRIGHT file at the top level of this repository contains the
|
# The COPYRIGHT file at the top level of this repository contains the
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.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.transaction import Transaction
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from trytond.pyson import Eval, Bool
|
from trytond.pyson import Eval
|
||||||
|
|
||||||
|
|
||||||
class Rate(SymbolMixin, ModelSQL, ModelView):
|
class Rate(SymbolMixin, ModelSQL, ModelView):
|
||||||
'Rate'
|
'Rate'
|
||||||
__name__ = 'investment.rate'
|
__name__ = 'investment.rate'
|
||||||
|
|
||||||
asset = fields.Many2One(string='Asset', required=True,
|
asset = fields.Many2One(
|
||||||
select=True, ondelete='CASCADE',
|
string='Asset', required=True, ondelete='CASCADE',
|
||||||
model_name='investment.asset')
|
model_name='investment.asset')
|
||||||
date = fields.Date(string='Date', required=True, select=True)
|
date = fields.Date(string='Date', required=True)
|
||||||
rate = fields.Numeric(string='Rate', required=True,
|
rate = fields.Numeric(
|
||||||
digits=(16, Eval('asset_digits', 4)),
|
string='Rate', required=True,
|
||||||
depends=['asset_digits'])
|
digits=(16, Eval('asset_digits', 4)), depends=['asset_digits'])
|
||||||
|
|
||||||
asset_digits = fields.Function(fields.Integer(string='Digits',
|
asset_digits = fields.Function(fields.Integer(
|
||||||
readonly=True), 'get_rate_data')
|
string='Digits', readonly=True), 'get_rate_data')
|
||||||
currency = fields.Function(fields.Many2One(string='Currency',
|
currency = fields.Function(fields.Many2One(
|
||||||
readonly=True, model_name='currency.currency'),
|
string='Currency', readonly=True, model_name='currency.currency'),
|
||||||
'get_rate_data')
|
'get_rate_data')
|
||||||
uom = fields.Function(fields.Many2One(string='Uom',
|
uom = fields.Function(fields.Many2One(
|
||||||
readonly=True, model_name='product.uom'), 'get_rate_data')
|
string='Uom', readonly=True, model_name='product.uom'),
|
||||||
symbol = fields.Function(fields.Char(string='Symbol',
|
'get_rate_data')
|
||||||
readonly=True), 'get_rate_data')
|
symbol = fields.Function(fields.Char(
|
||||||
|
string='Symbol', readonly=True), 'get_rate_data')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __setup__(cls):
|
def __setup__(cls):
|
||||||
|
@ -44,6 +46,21 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
|
||||||
'currency.msg_rate_positive'),
|
'currency.msg_rate_positive'),
|
||||||
]
|
]
|
||||||
cls._order.insert(0, ('date', 'DESC'))
|
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
|
@classmethod
|
||||||
def default_date(cls):
|
def default_date(cls):
|
||||||
|
@ -62,7 +79,8 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
|
||||||
tab_rate = cls.__table__()
|
tab_rate = cls.__table__()
|
||||||
cursor = Transaction().connection.cursor()
|
cursor = Transaction().connection.cursor()
|
||||||
|
|
||||||
query = tab_asset.join(tab_rate,
|
query = tab_asset.join(
|
||||||
|
tab_rate,
|
||||||
condition=tab_asset.id == tab_rate.asset,
|
condition=tab_asset.id == tab_rate.asset,
|
||||||
).select(
|
).select(
|
||||||
tab_rate.id,
|
tab_rate.id,
|
||||||
|
@ -87,34 +105,4 @@ class Rate(SymbolMixin, ModelSQL, ModelView):
|
||||||
result[n][record[0]] = r1[n]
|
result[n][record[0]] = r1[n]
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, vlist):
|
|
||||||
""" clear cache
|
|
||||||
"""
|
|
||||||
Asset = Pool().get('investment.asset')
|
|
||||||
|
|
||||||
Asset.values_cache_clear(
|
|
||||||
[x['asset'] for x in vlist],
|
|
||||||
['change_day1', 'change_month1', 'change_month3',
|
|
||||||
'change_month6', 'change_month12'],
|
|
||||||
)
|
|
||||||
return super(Rate, cls).create(vlist)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def write(cls, *args):
|
|
||||||
""" clear cache
|
|
||||||
"""
|
|
||||||
Asset = Pool().get('investment.asset')
|
|
||||||
|
|
||||||
asset_ids = []
|
|
||||||
actions = iter(args)
|
|
||||||
for rates, values in zip(actions, actions):
|
|
||||||
asset_ids.extend([x.asset.id for x in rates])
|
|
||||||
|
|
||||||
if len(asset_ids) > 0:
|
|
||||||
Asset.values_cache_clear(asset_ids,
|
|
||||||
['change_day1', 'change_month1', 'change_month3',
|
|
||||||
'change_month6', 'change_month12'])
|
|
||||||
super(Rate, cls).write(*args)
|
|
||||||
|
|
||||||
# Rate
|
# Rate
|
||||||
|
|
31
setup.py
31
setup.py
|
@ -2,7 +2,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Always prefer setuptools over distutils
|
# Always prefer setuptools over distutils
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup
|
||||||
# To use a consistent encoding
|
# To use a consistent encoding
|
||||||
from codecs import open
|
from codecs import open
|
||||||
from os import path
|
from os import path
|
||||||
|
@ -39,7 +39,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
|
||||||
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
|
# tryton-version
|
||||||
major_version = 6
|
major_version = 7
|
||||||
minor_version = 0
|
minor_version = 0
|
||||||
|
|
||||||
requires = ['requests>=2.26', 'html2text']
|
requires = ['requests>=2.26', 'html2text']
|
||||||
|
@ -51,23 +51,27 @@ for dep in info.get('depends', []):
|
||||||
prefix = modversion[dep]['prefix']
|
prefix = modversion[dep]['prefix']
|
||||||
|
|
||||||
if len(modversion[dep]['max']) > 0:
|
if len(modversion[dep]['max']) > 0:
|
||||||
requires.append('%s_%s >= %s, <= %s' %
|
requires.append('%s_%s >= %s, <= %s' % (
|
||||||
(prefix, dep, modversion[dep]['min'], modversion[dep]['max']))
|
prefix, dep, modversion[dep]['min'],
|
||||||
|
modversion[dep]['max']))
|
||||||
else:
|
else:
|
||||||
requires.append('%s_%s >= %s' %
|
requires.append('%s_%s >= %s' % (
|
||||||
(prefix, dep, modversion[dep]['min']))
|
prefix, dep, modversion[dep]['min']))
|
||||||
else:
|
else:
|
||||||
requires.append('%s_%s >= %s.%s, < %s.%s' %
|
requires.append('%s_%s >= %s.%s, < %s.%s' % (
|
||||||
('trytond', dep, major_version, minor_version,
|
'trytond', dep, major_version, minor_version,
|
||||||
major_version, minor_version + 1))
|
major_version, minor_version + 1))
|
||||||
requires.append('trytond >= %s.%s, < %s.%s' %
|
requires.append('trytond >= %s.%s, < %s.%s' % (
|
||||||
(major_version, minor_version, major_version, minor_version + 1))
|
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'),
|
version=info.get('version', '0.0.1'),
|
||||||
description='Tryton module to add investment items.',
|
description='Tryton module to add investment items.',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
|
long_description_content_type='text/x-rst',
|
||||||
url='https://www.m-ds.de/',
|
url='https://www.m-ds.de/',
|
||||||
|
download_url='https://scmdev.m-ds.de/Tryton/Extra/investment',
|
||||||
author='martin-data services',
|
author='martin-data services',
|
||||||
author_email='service@m-ds.de',
|
author_email='service@m-ds.de',
|
||||||
license='GPL-3',
|
license='GPL-3',
|
||||||
|
@ -87,6 +91,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
'Programming Language :: Python :: 3.7',
|
'Programming Language :: Python :: 3.7',
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
|
'Programming Language :: Python :: 3.9',
|
||||||
],
|
],
|
||||||
|
|
||||||
keywords='tryton investment shares commodities',
|
keywords='tryton investment shares commodities',
|
||||||
|
@ -95,8 +100,8 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
||||||
'trytond.modules.%s' % MODULE,
|
'trytond.modules.%s' % MODULE,
|
||||||
],
|
],
|
||||||
package_data={
|
package_data={
|
||||||
'trytond.modules.%s' % MODULE: (info.get('xml', [])
|
'trytond.modules.%s' % MODULE: (info.get('xml', []) + [
|
||||||
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
|
'tryton.cfg', 'locale/*.po', 'tests/*.py',
|
||||||
'view/*.xml', 'icon/*.svg', 'scripts/*.py',
|
'view/*.xml', 'icon/*.svg', 'scripts/*.py',
|
||||||
'versiondep.txt', 'README.rst']),
|
'versiondep.txt', 'README.rst']),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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="rgxrate">\nKurs\s+([\d+\.]*\d+,\d+)\s+USD\s*\n</field>
|
||||||
<field name="rgxdecimal">,</field>
|
<field name="rgxdecimal">,</field>
|
||||||
</record>
|
</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">
|
<record model="investment.source" id="web_markets_ft_com">
|
||||||
<field name="name">Financial Times UK</field>
|
<field name="name">Financial Times UK</field>
|
||||||
<field name="url">https://markets.ft.com/data/etfs/tearsheet/summary?s=${symbol}</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="name">www.sbroker.de</field>
|
||||||
<field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field>
|
<field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field>
|
||||||
<field name="nohtml" eval="True"/>
|
<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="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="rgxdecimal">,</field>
|
||||||
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
|
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
|
||||||
<field name="rgxidtype">isin</field>
|
<field name="rgxidtype">isin</field>
|
||||||
</record>
|
</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>
|
</data>
|
||||||
</tryton>
|
</tryton>
|
||||||
|
|
|
@ -1,30 +1,2 @@
|
||||||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||||
# this repository contains the full copyright notices and license terms.
|
# 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
770
tests/asset.py
Normal 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
50
tests/rate.py
Normal 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
185
tests/source.py
Normal 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
|
|
@ -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
26
tests/test_module.py
Normal 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
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
78
tests/wizard.py
Normal 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
|
|
@ -1,5 +1,5 @@
|
||||||
[tryton]
|
[tryton]
|
||||||
version=6.0.21
|
version=7.0.0
|
||||||
depends:
|
depends:
|
||||||
ir
|
ir
|
||||||
res
|
res
|
||||||
|
|
|
@ -29,10 +29,9 @@ class UpdateSoureWizard(Wizard):
|
||||||
if OnlineSource.update_rate(asset):
|
if OnlineSource.update_rate(asset):
|
||||||
to_run_activities.append(asset)
|
to_run_activities.append(asset)
|
||||||
|
|
||||||
if len(to_run_activities) > 0:
|
if to_run_activities:
|
||||||
Asset.after_update_actions(to_run_activities)
|
Asset.after_update_actions(to_run_activities)
|
||||||
|
|
||||||
return 'end'
|
return 'end'
|
||||||
|
|
||||||
# UpdateSoureWizard
|
# UpdateSoureWizard
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
diagram;6.0.7;6.0.999;mds
|
|
||||||
|
|
|
@ -60,6 +60,11 @@ full copyright notices and license terms. -->
|
||||||
<label name="updtdays"/>
|
<label name="updtdays"/>
|
||||||
<field name="updtdays"/>
|
<field name="updtdays"/>
|
||||||
|
|
||||||
|
<label name="updturl"/>
|
||||||
|
<field name="updturl" colspan="3"/>
|
||||||
|
<field name="updturl_enable"/>
|
||||||
|
<newline/>
|
||||||
|
|
||||||
<field name="updtsources" colspan="4"/>
|
<field name="updtsources" colspan="4"/>
|
||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
|
|
|
@ -4,12 +4,12 @@ The COPYRIGHT file at the top level of this repository contains the
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<tree>
|
<tree>
|
||||||
<field name="name" expand="1"/>
|
<field name="name" expand="1"/>
|
||||||
<field name="change_day1" symbol="change_symbol"/>
|
<field name="change_day1" optional="0"/>
|
||||||
<field name="change_month1" symbol="change_symbol"/>
|
<field name="change_month1" optional="0"/>
|
||||||
<field name="change_month3" symbol="change_symbol"/>
|
<field name="change_month3" optional="0"/>
|
||||||
<field name="change_month6" symbol="change_symbol"/>
|
<field name="change_month6" optional="0"/>
|
||||||
<field name="date"/>
|
<field name="date" optional="0"/>
|
||||||
<field name="rate" symbol="asset_symbol"/>
|
<field name="rate" symbol="asset_symbol" optional="0"/>
|
||||||
<field name="isin"/>
|
<field name="isin" optional="0"/>
|
||||||
<field name="wkn" />
|
<field name="wkn" optional="0"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
|
@ -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>
|
|
|
@ -5,16 +5,24 @@ full copyright notices and license terms. -->
|
||||||
<form col="6">
|
<form col="6">
|
||||||
<label name="name"/>
|
<label name="name"/>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<label name="query_method"/>
|
||||||
|
<field name="query_method"/>
|
||||||
|
<newline/>
|
||||||
|
|
||||||
|
<separator colspan="6" name="url" string="Method: Extract from web page"/>
|
||||||
<label name="url"/>
|
<label name="url"/>
|
||||||
<field name="url" colspan="3"/>
|
<field name="url" colspan="3"/>
|
||||||
|
|
||||||
<label name="nohtml"/>
|
<label name="nohtml"/>
|
||||||
<field name="nohtml"/>
|
<field name="nohtml"/>
|
||||||
<label id="labtp1" string=" "/>
|
|
||||||
<label xalign="0.0" colspan="3" id="labtempl"
|
|
||||||
string="URL parameter placeholders: ${isin}, ${nsin}, ${symbol}"/>
|
|
||||||
|
|
||||||
<separator colspan="6" id="seprgx" string="Regular expressions to find data"/>
|
<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"/>
|
||||||
<label name="rgxdate"/>
|
<label name="rgxdate"/>
|
||||||
<field name="rgxdate"/>
|
<field name="rgxdate"/>
|
||||||
<label name="rgxdatefmt"/>
|
<label name="rgxdatefmt"/>
|
||||||
|
|
|
@ -4,5 +4,6 @@ The COPYRIGHT file at the top level of this repository contains the
|
||||||
full copyright notices and license terms. -->
|
full copyright notices and license terms. -->
|
||||||
<tree>
|
<tree>
|
||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
|
<field name="query_method"/>
|
||||||
<field name="url"/>
|
<field name="url"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|
Loading…
Reference in a new issue