Compare commits
64 commits
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 |
45 changed files with 3516 additions and 993 deletions
14
README.rst
14
README.rst
|
@ -9,11 +9,19 @@ pip install mds-investment
|
|||
|
||||
Requires
|
||||
========
|
||||
- Tryton 6.0
|
||||
- Tryton 7.0
|
||||
|
||||
How to
|
||||
======
|
||||
|
||||
Store values such as stocks, funds and commodities in Tryton.
|
||||
Watch the performance. The Tryton module periodically loads
|
||||
quotes of values from one or more price sources.
|
||||
You can define the course sources yourself.
|
||||
|
||||
Changes
|
||||
=======
|
||||
|
||||
*6.0.0 - 09.11.2022*
|
||||
*7.0.0 - 01.12.2023*
|
||||
|
||||
- init
|
||||
- compatibility to Tryton 7.0
|
||||
|
|
14
__init__.py
14
__init__.py
|
@ -1,26 +1,34 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from trytond.pool import Pool
|
||||
from .asset import Asset
|
||||
from .asset import Asset, AssetSourceRel
|
||||
from .rate import Rate
|
||||
from .identifier import Identifier
|
||||
from .cron import Cron
|
||||
from .onlinesource import OnlineSource
|
||||
from .update_wiz import UpdateSoureWizard
|
||||
from .diagram import GraphDef, ChartPoint
|
||||
from .import_wiz import ImportWizard, ImportWizardStart
|
||||
|
||||
|
||||
def register():
|
||||
Pool.register(
|
||||
OnlineSource,
|
||||
AssetSourceRel,
|
||||
Asset,
|
||||
Rate,
|
||||
Identifier,
|
||||
Cron,
|
||||
ImportWizardStart,
|
||||
module='investment', type_='model')
|
||||
Pool.register(
|
||||
GraphDef,
|
||||
ChartPoint,
|
||||
module='investment', type_='model', depends=['diagram'])
|
||||
Pool.register(
|
||||
UpdateSoureWizard,
|
||||
ImportWizard,
|
||||
module='investment', type_='wizard')
|
||||
|
||||
|
|
25
asset.xml
25
asset.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
@ -35,6 +35,29 @@ full copyright notices and license terms. -->
|
|||
<field name="act_window" ref="act_asset_view"/>
|
||||
</record>
|
||||
|
||||
<!-- domain view -->
|
||||
<record model="ir.action.act_window.domain" id="act_asset_domain_current">
|
||||
<field name="name">Current</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="[('date', '>=', Date(delta_days=-5))]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_asset_view"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_asset_domain_inactive">
|
||||
<field name="name">Inactive</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="[('date', '<', Date(delta_days=-5))]" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_asset_view"/>
|
||||
</record>
|
||||
<record model="ir.action.act_window.domain" id="act_asset_domain_all">
|
||||
<field name="name">All</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="domain" eval="" pyson="1"/>
|
||||
<field name="count" eval="True"/>
|
||||
<field name="act_window" ref="act_asset_view"/>
|
||||
</record>
|
||||
|
||||
<!-- permission -->
|
||||
<!-- anon: deny all -->
|
||||
<record model="ir.model.access" id="access_asset-anon">
|
||||
|
|
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
|
2
cron.py
2
cron.py
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
|
|
2
cron.xml
2
cron.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
|
148
diagram.py
Normal file
148
diagram.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from trytond.model import fields
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.pyson import Eval
|
||||
from sql.functions import Function
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
class Concat2(Function):
|
||||
""" concat columns
|
||||
"""
|
||||
__slots__ = ()
|
||||
_function = 'concat'
|
||||
|
||||
# end Concat2
|
||||
|
||||
|
||||
class GraphDef(metaclass=PoolMeta):
|
||||
__name__ = 'diagram.graphdef'
|
||||
|
||||
asset = fields.Many2One(
|
||||
string='Asset', model_name='investment.asset',
|
||||
states={
|
||||
'invisible': Eval('dtype', '') != 'investment.asset',
|
||||
'required': Eval('dtype', '') == 'investment.asset',
|
||||
}, depends=['dtype'])
|
||||
|
||||
@classmethod
|
||||
def _get_dtypes(cls):
|
||||
""" return list of types
|
||||
"""
|
||||
l1 = super(GraphDef, cls)._get_dtypes()
|
||||
l1.append('investment.asset')
|
||||
return l1
|
||||
|
||||
def get_recname_value(self):
|
||||
""" value by dtype
|
||||
"""
|
||||
if self.dtype == 'investment.asset':
|
||||
return getattr(self.asset, 'rec_name', '-')
|
||||
return super(GraphDef, self).get_recname_value()
|
||||
|
||||
def get_field_key(self):
|
||||
""" get to read value from json
|
||||
"""
|
||||
if self.dtype == 'investment.asset':
|
||||
return 'asset%d' % self.asset.id
|
||||
return super(GraphDef, self).get_field_key()
|
||||
|
||||
def get_scaling_for_investment_asset(self):
|
||||
""" get scaling for currency
|
||||
"""
|
||||
Rate = Pool().get('investment.rate')
|
||||
|
||||
if self.scaling == 'fix':
|
||||
return None
|
||||
|
||||
if self.scaling == 'alldata':
|
||||
query = [('asset', '=', self.asset.id)]
|
||||
elif self.scaling == 'view':
|
||||
query = [
|
||||
('asset', '=', self.asset.id),
|
||||
('date', '>=', self.chart.used_start_date()),
|
||||
('date', '<=', self.chart.used_end_date()),
|
||||
]
|
||||
elif self.scaling == 'six':
|
||||
query = [
|
||||
('asset', '=', self.asset.id),
|
||||
('date', '>=', self.chart.used_start_date() -
|
||||
timedelta(days=180)),
|
||||
('date', '<=', self.chart.used_end_date()),
|
||||
]
|
||||
|
||||
min_rec = Rate.search(query, limit=1, order=[('rate', 'ASC')])
|
||||
max_rec = Rate.search(query, limit=1, order=[('rate', 'DESC')])
|
||||
min_val = min_rec[0].rate if len(min_rec) > 0 else None
|
||||
max_val = max_rec[0].rate if len(max_rec) > 0 else None
|
||||
|
||||
return self.compute_scaling_factor(min_val, max_val)
|
||||
|
||||
# end GraphDef
|
||||
|
||||
|
||||
class ChartPoint(metaclass=PoolMeta):
|
||||
__name__ = 'diagram.point'
|
||||
|
||||
@classmethod
|
||||
def get_interpolated_val(cls, keyname, query_date):
|
||||
""" query two neighbour-values to
|
||||
interpolate missing value
|
||||
"""
|
||||
Rate = Pool().get('investment.rate')
|
||||
|
||||
if not keyname:
|
||||
return None
|
||||
|
||||
# check if query is for us
|
||||
if keyname.startswith('asset'):
|
||||
asset_id = int(keyname[len('asset'):])
|
||||
|
||||
before = Rate.search([
|
||||
('date', '<', query_date),
|
||||
('asset', '=', asset_id),
|
||||
], limit=1, order=[('date', 'DESC')])
|
||||
|
||||
after = Rate.search([
|
||||
('date', '>', query_date),
|
||||
('asset', '=', asset_id),
|
||||
], limit=1, order=[('date', 'ASC')])
|
||||
|
||||
if (len(before) == 1) and (len(after) == 1):
|
||||
result = cls.interpolate_linear(
|
||||
(after[0].date, after[0].rate),
|
||||
(before[0].date, before[0].rate),
|
||||
query_date
|
||||
)
|
||||
return result
|
||||
elif len(before) == 1:
|
||||
return before[0].rate
|
||||
elif len(after) == 1:
|
||||
return after[0].rate
|
||||
return super(ChartPoint, cls).get_interpolated_val(keyname, query_date)
|
||||
|
||||
@classmethod
|
||||
def get_table_parts(cls):
|
||||
""" return a list of tables to union,
|
||||
table must contain the columns:
|
||||
date, key, val
|
||||
"""
|
||||
pool = Pool()
|
||||
Rate = pool.get('investment.rate')
|
||||
tab_rate = Rate.__table__()
|
||||
|
||||
tabparts = super(ChartPoint, cls).get_table_parts()
|
||||
|
||||
# rate
|
||||
tabparts.append(tab_rate.select(
|
||||
tab_rate.date,
|
||||
Concat2('asset', tab_rate.asset).as_('key'),
|
||||
tab_rate.rate.as_('val'),
|
||||
))
|
||||
return tabparts
|
||||
|
||||
# end ChartPoint
|
16
diagram.xml
Normal file
16
diagram.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
<data depends="diagram">
|
||||
|
||||
<!-- views -->
|
||||
<record model="ir.ui.view" id="graph_view_form">
|
||||
<field name="model">diagram.graphdef</field>
|
||||
<field name="inherit" ref="diagram.graph_view_form"/>
|
||||
<field name="name">graph_form</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
|
12
icon.xml
12
icon.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the cashbook-module from m-ds for Tryton.
|
||||
<!-- 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. -->
|
||||
<tryton>
|
||||
|
@ -10,5 +10,15 @@ full copyright notices and license terms. -->
|
|||
<field name="path">icon/gain_invest.svg</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.icon" id="stockonline_icon">
|
||||
<field name="name">mds-stockonline</field>
|
||||
<field name="path">icon/stockonline.svg</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.icon" id="asset_icon">
|
||||
<field name="name">mds-asset</field>
|
||||
<field name="path">icon/asset.svg</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
5
icon/asset.svg
Normal file
5
icon/asset.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g><path d="M729.7,990c-143.8,0-260.3-116.6-260.3-260.3c0-143.8,116.5-260.3,260.3-260.3S990,585.9,990,729.7C990,873.4,873.4,990,729.7,990L729.7,990z M729.7,530.6c-109.9,0-199.1,89.1-199.1,199.1c0,109.9,89.1,199.1,199.1,199.1s199.1-89.1,199.1-199.1C928.7,619.8,839.6,530.6,729.7,530.6L729.7,530.6z M852.2,775.6H699.1c-8.5,0-15.3-6.9-15.3-15.3V607.2c0-8.5,6.9-15.3,15.3-15.3h30.6c8.5,0,15.3,6.9,15.3,15.3v107.2h107.2c8.5,0,15.3,6.9,15.3,15.3v30.6C867.5,768.8,860.6,775.6,852.2,775.6L852.2,775.6z M836.9,101.9c0-16.9-13.7-30.6-30.6-30.6H101.9C85,71.3,71.2,85,71.2,101.9v796.3c0,16.9,13.7,30.6,30.6,30.6h306.2V990H71.2C37.4,990,10,962.5,10,928.7V71.2C10,37.4,37.4,10,71.2,10h765.6c33.8,0,61.3,27.4,61.3,61.3v275.6h-61.3V101.9L836.9,101.9z M239.7,683.7h122.5c8.4,0,15.3,6.9,15.3,15.3v30.6c0,8.4-6.9,15.3-15.3,15.3H239.7c-8.5,0-15.3-6.9-15.3-15.3v-30.6C224.4,690.6,231.2,683.7,239.7,683.7L239.7,683.7z M224.4,301v-30.6c0-8.5,6.8-15.3,15.3-15.3h459.4c8.5,0,15.3,6.9,15.3,15.3V301c0,8.4-6.9,15.3-15.3,15.3H239.7C231.2,316.3,224.4,309.4,224.4,301L224.4,301z M224.4,515.3v-30.6c0-8.5,6.8-15.3,15.3-15.3h214.4c8.5,0,15.3,6.9,15.3,15.3v30.6c0,8.4-6.9,15.3-15.3,15.3H239.7C231.2,530.6,224.4,523.7,224.4,515.3L224.4,515.3z"/></g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
5
icon/stockonline.svg
Normal file
5
icon/stockonline.svg
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
|
||||
<g><g><g><path d="M636.1,146.1c-34.8,0-68.3,5.1-100.1,14.5c-2.7,6.1-5.3,12.2-7.8,18.3l0,0c-3.4,8.3-6.8,16.6-9.9,25c23.8-18.5,50.1-31.5,77.9-38c-17.1,17.7-32.3,46.5-44.7,83.7c-18.2,3.3-35.8,7.5-52.7,12.7c-1.7,5.8-3.2,11.6-4.8,17.4c16.8-5.6,34.4-10.2,52.5-13.8c-10.2,34.9-18.1,75.9-23.1,121.3c-18.3,2-35.9,4.4-52.6,7.3c-0.7,5.2-1.4,10.4-2,15.6c16.7-3,34.5-5.6,53.1-7.6c-1.4,14.7-2.4,29.8-3.2,45.2c1.2-0.1,2.4-0.3,3.7-0.3h11.4c0.8-16,1.9-31.6,3.3-46.5c30.8-2.9,63.9-4.6,99.1-4.6c35.2,0,68.3,1.7,99.1,4.6c2.9,30.8,4.6,63.9,4.6,99.1c0,35.2-1.6,68.3-4.6,99.1c-30.8,2.9-63.9,4.6-99.1,4.6s-68.3-1.7-99.1-4.6c-1.8-18.7-3-38.4-3.8-58.7l-13.6,30.4c0.6,9,1.3,18,2.1,26.7c-4.5-0.5-8.9-1.1-13.3-1.6l-6.4,14.3c7,0.9,14,1.8,21.2,2.6c5,45.4,12.9,86.5,23.1,121.3c-29.1-5.7-56.6-14.2-82.1-25c0.9,5.9,1.9,11.7,3,17.5c26.2,10.4,54.4,18.4,84.1,23.7c12.4,37.3,27.7,66.1,44.8,83.8c-47-10.9-89.5-40.5-123.3-83.4c3.4,12.2,7.5,24.2,12,35.8c13.2,13.2,27.4,24.7,42.4,34.2c-11.5-3.9-22.7-8.4-33.6-13.5c3.1,6.9,6.2,13.8,9.4,20.6c41.1,16.7,85.9,26,132.9,26c195.2,0,354-158.8,354-354C990,304.8,831.2,146.1,636.1,146.1z M889.2,432.1c54.6,19.3,85.7,44,85.7,67.9c0,23.9-31.2,48.6-85.7,67.9c3.1-22,4.8-44.7,4.8-67.9C894,476.8,892.3,454.1,889.2,432.1z M886.5,415.3c-6.4-35.7-16.7-69.2-30.4-99.7C915.4,352,957,402.7,970.3,460.2C952.6,443,923.8,427.8,886.5,415.3z M750.4,402.5c47.5,5.3,88.8,13.8,122.7,24.2c3.8,23.6,5.8,48.1,5.8,73.3c0,25.1-2,49.6-5.8,73.3c-33.9,10.5-75.2,18.9-122.7,24.2c2.9-30.9,4.4-63.5,4.4-97.5C754.9,466,753.3,433.4,750.4,402.5z M956.9,391.3c-25.5-39.7-64.4-74-112.5-99.6c-25.6-48.1-59.8-87-99.6-112.5C844.3,213,923.1,291.8,956.9,391.3z M820.5,280c-30.5-13.6-64.1-23.9-99.7-30.3c-12.4-37.3-27.7-66.1-44.8-83.8C733.4,179.1,784.1,220.7,820.5,280z M636.1,161.2c23.9,0,48.6,31.2,67.9,85.7c-22-3.1-44.7-4.8-67.9-4.8c-23.2,0-46,1.7-67.9,4.8C587.4,192.3,612.2,161.2,636.1,161.2z M636.1,381.3c-34,0-66.6,1.6-97.5,4.5c5.3-47.5,13.8-88.9,24.3-122.7c23.6-3.8,48.1-5.8,73.2-5.8c25.2,0,49.7,2,73.3,5.8c10.5,33.9,18.9,75.2,24.2,122.7C702.7,382.8,670,381.3,636.1,381.3z M725.8,265.9c39.1,7.7,75.4,20.3,107.5,36.9c16.5,32.1,29.1,68.4,36.8,107.5c-34.9-10.2-75.9-18.1-121.3-23.1C743.9,341.8,736,300.8,725.8,265.9z M748.9,612.8c45.4-5,86.4-12.9,121.3-23.1c-7.7,39.1-20.3,75.4-36.8,107.5c-32.1,16.5-68.4,29.1-107.5,36.9C736,699.3,743.9,658.2,748.9,612.8z M538.6,614.3c30.9,2.9,63.5,4.4,97.5,4.4c34,0,66.6-1.6,97.5-4.4c-5.3,47.5-13.8,88.8-24.2,122.7c-23.6,3.7-48.1,5.8-73.3,5.8c-25.2,0-49.7-2.1-73.3-5.8C552.3,703.1,543.9,661.8,538.6,614.3z M636.1,838.8c-23.9,0-48.6-31.1-67.9-85.7c22,3.1,44.7,4.8,67.9,4.8c23.2,0,45.9-1.7,67.9-4.8C684.7,807.7,660,838.8,636.1,838.8z M720.8,750.4c35.7-6.4,69.2-16.7,99.7-30.3c-36.4,59.3-87.1,100.8-144.6,114.2C693.1,816.6,708.3,787.7,720.8,750.4z M744.8,820.8c39.7-25.5,74-64.4,99.6-112.5c48.1-25.6,87-59.9,112.5-99.6C923.1,708.2,844.3,787,744.8,820.8z M856.1,684.4c13.6-30.5,23.9-64.1,30.4-99.7c37.3-12.4,66.1-27.7,83.8-44.9C957,597.3,915.4,648.1,856.1,684.4z"/><path d="M460,612.9L512,496.6h136.3c8.3,0,15.1-6.8,15.1-15.1c0-8.3-6.8-15.1-15.1-15.1H502.1c-6,0-11.4,3.5-13.8,9l-56.7,127.1c-0.6,0-1.1-0.1-1.7-0.1c-1.5,0-3,0.1-4.5,0.2l-90-252.2c11.4-8.9,18.8-22.7,18.8-38.2c0-26.7-21.7-48.4-48.4-48.4c-26.7,0-48.4,21.7-48.4,48.4c0,12.6,4.9,24.1,12.9,32.8l-58.9,105.8H104.4c-6.4-19.3-24.6-33.3-46-33.3c-26.7,0-48.4,21.7-48.4,48.4c0,26.7,21.7,48.4,48.4,48.4c21.5,0,39.7-14,46-33.4h115.7c5.5,0,10.5-3,13.2-7.8l63.2-113.5c3,0.6,6,0.9,9.2,0.9c0.4,0,0.8-0.1,1.2-0.1l90.7,254.2c-9.9,8.9-16.2,21.7-16.2,36.1c0,26.7,21.7,48.4,48.4,48.4c26.7,0,48.4-21.7,48.4-48.4C478.4,635.5,471.2,621.8,460,612.9z M58.4,506.8c-22.5,0-40.9-18.3-40.9-40.9c0-22.5,18.3-40.9,40.9-40.9c17.2,0,31.9,10.7,37.9,25.7H58.4c-8.3,0-15.1,6.8-15.1,15.1c0,8.3,6.8,15.1,15.1,15.1h38C90.4,496,75.7,506.8,58.4,506.8z M264.8,312.2c0-22.5,18.3-40.9,40.9-40.9c22.5,0,40.9,18.3,40.9,40.9c0,12.2-5.4,23.1-13.9,30.6l-12.8-35.7c-2-5.6-7.1-9.5-13-10c-5.9-0.5-11.6,2.5-14.4,7.7L274,338C268.3,330.9,264.8,322,264.8,312.2z M300.4,352.7l2.2-4l1.5,4.3C303,352.9,301.7,352.8,300.4,352.7z M428.2,610l-0.1,0.2l-0.1-0.2C428.1,610,428.2,610,428.2,610z M429.9,691.7c-22.5,0-40.9-18.3-40.9-40.9c0-11,4.4-20.9,11.4-28.3l11.9,33.4c2.1,5.8,7.5,9.8,13.7,10c0.2,0,0.4,0,0.6,0c5.9,0,11.4-3.5,13.8-9l16.4-36.8c8.5,7.5,14,18.5,14,30.7C470.8,673.3,452.5,691.7,429.9,691.7z"/></g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g><g></g></g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from trytond.model import fields
|
||||
from trytond.pool import PoolMeta
|
||||
|
||||
|
||||
|
|
211
import_wiz.py
Normal file
211
import_wiz.py
Normal file
|
@ -0,0 +1,211 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from io import StringIO
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
import csv
|
||||
from trytond.pool import Pool
|
||||
from trytond.model import ModelView, fields
|
||||
from trytond.wizard import Wizard, StateTransition, StateView, Button
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
|
||||
|
||||
sel_dec_divider = [
|
||||
(',', ','),
|
||||
('.', '.'),
|
||||
]
|
||||
|
||||
sel_date_fmt = [
|
||||
('%d.%m.%Y', 'dd.mm.yyyy'),
|
||||
('%Y-%m-%d', 'yyyy-mm-dd'),
|
||||
('%m/%d/%Y', 'mm/dd/yyyy'),
|
||||
]
|
||||
|
||||
sel_field_delimiter = [
|
||||
(';', ';'),
|
||||
(',', ','),
|
||||
]
|
||||
|
||||
|
||||
class ImportWizardStart(ModelView):
|
||||
'Import CSV-File'
|
||||
__name__ = 'investment.imp_wiz.start'
|
||||
|
||||
asset = fields.Many2One(
|
||||
string='Asset', readonly=True, model_name='investment.asset')
|
||||
file_ = fields.Binary(string="CSV-File", required=True)
|
||||
dec_divider = fields.Selection(
|
||||
string='Decimal divider', required=True, selection=sel_dec_divider)
|
||||
date_fmt = fields.Selection(
|
||||
string='Date format', required=True, selection=sel_date_fmt)
|
||||
field_delimiter = fields.Selection(
|
||||
string='Field delimiter', required=True, selection=sel_field_delimiter)
|
||||
|
||||
# end ImportWizardStart
|
||||
|
||||
|
||||
class ImportWizard(Wizard):
|
||||
'Import CSV-File'
|
||||
__name__ = 'investment.imp_wiz'
|
||||
|
||||
start_state = 'start'
|
||||
start = StateView(
|
||||
model_name='investment.imp_wiz.start',
|
||||
view='investment.imp_wiz_start_form',
|
||||
buttons=[
|
||||
Button(string='Cancel', state='end', icon='tryton-cancel'),
|
||||
Button(
|
||||
string='Import File', state='importf',
|
||||
icon='tryton-import', default=True),
|
||||
])
|
||||
importf = StateTransition()
|
||||
|
||||
def default_start(self, fields):
|
||||
""" show asset
|
||||
"""
|
||||
context = Transaction().context
|
||||
|
||||
values = {
|
||||
'dec_divider': ',',
|
||||
'date_fmt': '%d.%m.%Y',
|
||||
'field_delimiter': ';',
|
||||
}
|
||||
values['asset'] = context.get('active_id', None)
|
||||
return values
|
||||
|
||||
def transition_importf(self):
|
||||
""" read file, import
|
||||
"""
|
||||
pool = Pool()
|
||||
ImportWiz = pool.get('investment.imp_wiz', type='wizard')
|
||||
|
||||
if self.start.file_:
|
||||
(lines, max_date, min_date) = ImportWiz.read_csv_file(
|
||||
self.start.file_.decode('utf8'),
|
||||
dec_divider=self.start.dec_divider,
|
||||
date_fmt=self.start.date_fmt,
|
||||
delimiter=self.start.field_delimiter)
|
||||
|
||||
if lines:
|
||||
ImportWiz.upload_rates(
|
||||
self.start.asset,
|
||||
lines, min_date, max_date)
|
||||
return 'end'
|
||||
|
||||
@classmethod
|
||||
def upload_rates(cls, asset, rates_list, min_date, max_date):
|
||||
""" upload new rates to asset
|
||||
"""
|
||||
Rate = Pool().get('investment.rate')
|
||||
|
||||
# get rate in date-range
|
||||
rates = Rate.search([
|
||||
('asset.id', '=', asset.id),
|
||||
('date', '>=', min_date),
|
||||
('date', '<=', max_date),
|
||||
])
|
||||
existing_dates = [x.date for x in rates]
|
||||
done_dates = []
|
||||
|
||||
to_create = []
|
||||
for rate in rates_list:
|
||||
if rate['date'] in existing_dates:
|
||||
continue
|
||||
if rate['date'] in done_dates:
|
||||
continue
|
||||
|
||||
to_create.append({
|
||||
'asset': asset.id,
|
||||
'date': rate['date'],
|
||||
'rate': rate['rate'],
|
||||
})
|
||||
done_dates.append(rate['date'])
|
||||
|
||||
if len(to_create) > 0:
|
||||
Rate.create(to_create)
|
||||
|
||||
@classmethod
|
||||
def read_csv_file(cls, file_content, dec_divider, date_fmt, delimiter):
|
||||
""" read file-content from csv
|
||||
"""
|
||||
result = []
|
||||
|
||||
del_chars = ['.', ',']
|
||||
del_chars.remove(dec_divider)
|
||||
min_date = None
|
||||
max_date = None
|
||||
min_rate = None
|
||||
max_rate = None
|
||||
|
||||
with StringIO(file_content) as fhdl:
|
||||
csv_lines = csv.DictReader(
|
||||
fhdl,
|
||||
fieldnames=['date', 'rate'],
|
||||
dialect='excel',
|
||||
delimiter=delimiter)
|
||||
|
||||
for line in csv_lines:
|
||||
# skip first line
|
||||
if line.get('date', '') == 'date':
|
||||
continue
|
||||
|
||||
try:
|
||||
date_val = datetime.strptime(
|
||||
line.get('date', None).strip(), date_fmt).date()
|
||||
except Exception:
|
||||
raise UserError(gettext(
|
||||
'investment.msg_import_err_date',
|
||||
datefmt=date_fmt,
|
||||
colnr='1',
|
||||
))
|
||||
try:
|
||||
rate_val = line.get('rate', None).replace(
|
||||
del_chars[0], '').strip()
|
||||
rate_val = Decimal(rate_val.replace(dec_divider, '.'))
|
||||
except Exception:
|
||||
raise UserError(gettext(
|
||||
'investment.msg_import_err_date',
|
||||
datefmt='dd%sdd' % dec_divider,
|
||||
colnr='2'))
|
||||
|
||||
if isinstance(date_val, date) and isinstance(
|
||||
rate_val, Decimal):
|
||||
result.append({'date': date_val, 'rate': rate_val})
|
||||
|
||||
# date range
|
||||
if max_date is None:
|
||||
max_date = date_val
|
||||
else:
|
||||
if max_date < date_val:
|
||||
max_date = date_val
|
||||
|
||||
if min_date is None:
|
||||
min_date = date_val
|
||||
else:
|
||||
if min_date > date_val:
|
||||
min_date = date_val
|
||||
|
||||
# rate range
|
||||
if max_rate is None:
|
||||
max_rate = rate_val
|
||||
else:
|
||||
if max_rate < rate_val:
|
||||
max_rate = rate_val
|
||||
|
||||
if min_rate is None:
|
||||
min_rate = rate_val
|
||||
else:
|
||||
if min_rate > rate_val:
|
||||
min_rate = rate_val
|
||||
else:
|
||||
raise UserError(gettext(
|
||||
'investment.msg_err_unknown_content',
|
||||
linetxt=line))
|
||||
return (result, max_date, min_date)
|
||||
|
||||
# end ImportWizard
|
31
import_wiz.xml
Normal file
31
import_wiz.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="imp_wiz_start_form">
|
||||
<field name="model">investment.imp_wiz.start</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">import_start_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="act_import_wizard">
|
||||
<field name="name">Import CSV-File</field>
|
||||
<field name="wiz_name">investment.imp_wiz</field>
|
||||
</record>
|
||||
<record model="ir.action-res.group"
|
||||
id="act_import_wizard-group_investment_admin">
|
||||
<field name="action" ref="act_import_wizard"/>
|
||||
<field name="group" ref="group_investment_admin"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.keyword" id="act_import_wizard-cat-keyword">
|
||||
<field name="keyword">form_action</field>
|
||||
<field name="model">investment.asset,-1</field>
|
||||
<field name="action" ref="act_import_wizard"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
254
locale/de.po
254
locale/de.po
|
@ -34,6 +34,26 @@ msgctxt "model:ir.message,text:msg_currency_rate_positive"
|
|||
msgid "A asset rate must be positive."
|
||||
msgstr "Der Kurs des Vermögenswertes muss positiv sein."
|
||||
|
||||
msgctxt "model:ir.message,text:msg_import_err_date"
|
||||
msgid "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)"
|
||||
msgstr "Fehler beim Lesen von Spalte %(colnr)s der Datei, erwartetes Datum (Format: %(datefmt)s)"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_err_unknown_content"
|
||||
msgid "failed to identify row content: %(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 #
|
||||
|
@ -66,6 +86,70 @@ msgctxt "model:ir.action,name:updt_source_wiz"
|
|||
msgid "Update Source"
|
||||
msgstr "Quelle aktualisieren"
|
||||
|
||||
msgctxt "model:ir.action,name:act_import_wizard"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "CSV-Datei importieren"
|
||||
|
||||
|
||||
###############################
|
||||
# ir.action.act_window.domain #
|
||||
###############################
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_current"
|
||||
msgid "Current"
|
||||
msgstr "Aktuell"
|
||||
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_inactive"
|
||||
msgid "Inactive"
|
||||
msgstr "Inaktiv"
|
||||
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_all"
|
||||
msgid "All"
|
||||
msgstr "Alle"
|
||||
|
||||
|
||||
############################
|
||||
# investment.imp_wiz.start #
|
||||
############################
|
||||
msgctxt "model:investment.imp_wiz.start,name:"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "CSV-Datei importieren"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,asset:"
|
||||
msgid "Asset"
|
||||
msgstr "Vermögenswert"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,file_:"
|
||||
msgid "CSV-File"
|
||||
msgstr "CSV-Datei"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,dec_divider:"
|
||||
msgid "Decimal divider"
|
||||
msgstr "Dezimaltrenner"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,date_fmt:"
|
||||
msgid "Date format"
|
||||
msgstr "Datumsformat"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,field_delimiter:"
|
||||
msgid "Field delimiter"
|
||||
msgstr "Feldtrenner"
|
||||
|
||||
|
||||
######################
|
||||
# investment.imp_wiz #
|
||||
######################
|
||||
msgctxt "model:investment.imp_wiz,name:"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "CSV-Datei importieren"
|
||||
|
||||
msgctxt "wizard_button:investment.imp_wiz,start,end:"
|
||||
msgid "Cancel"
|
||||
msgstr "Abbruch"
|
||||
|
||||
msgctxt "wizard_button:investment.imp_wiz,start,importf:"
|
||||
msgid "Import File"
|
||||
msgstr "Datei importieren"
|
||||
|
||||
|
||||
############################
|
||||
# investment.source_update #
|
||||
|
@ -86,6 +170,10 @@ msgctxt "view:investment.asset:"
|
|||
msgid "Currency and Units"
|
||||
msgstr "Währung und Einheiten"
|
||||
|
||||
msgctxt "view:investment.asset:"
|
||||
msgid "Gain and Loss"
|
||||
msgstr "Gewinn und Verlust"
|
||||
|
||||
msgctxt "view:investment.asset:"
|
||||
msgid "Identifiers"
|
||||
msgstr "Bezeichner"
|
||||
|
@ -102,21 +190,13 @@ msgctxt "field:investment.asset,company:"
|
|||
msgid "Company"
|
||||
msgstr "Unternehmen"
|
||||
|
||||
msgctxt "field:investment.asset,company_currency:"
|
||||
msgid "Company Currency"
|
||||
msgstr "Unternehmenswährung"
|
||||
|
||||
msgctxt "field:investment.asset,company_currency_digits:"
|
||||
msgid "Currency Digits (Ref.)"
|
||||
msgstr "Nachkommastellen Währung (Ref.)"
|
||||
|
||||
msgctxt "field:investment.asset,currency:"
|
||||
msgid "Currency"
|
||||
msgstr "Währung"
|
||||
|
||||
msgctxt "field:investment.asset,currency_digits:"
|
||||
msgid "Currency Digits"
|
||||
msgstr "Nachkommastellen Währung"
|
||||
msgid "Digits"
|
||||
msgstr "Nachkommastellen"
|
||||
|
||||
msgctxt "field:investment.asset,product:"
|
||||
msgid "Product"
|
||||
|
@ -134,6 +214,14 @@ msgctxt "field:investment.asset,uom:"
|
|||
msgid "UOM"
|
||||
msgstr "Einheit"
|
||||
|
||||
msgctxt "field:investment.asset,symbol:"
|
||||
msgid "UOM"
|
||||
msgstr "Einheit"
|
||||
|
||||
msgctxt "field:investment.asset,asset_symbol:"
|
||||
msgid "Symbol"
|
||||
msgstr "Symbol"
|
||||
|
||||
msgctxt "field:investment.asset,wkn:"
|
||||
msgid "NSIN"
|
||||
msgstr "WKN"
|
||||
|
@ -162,25 +250,121 @@ msgctxt "field:investment.asset,rates:"
|
|||
msgid "Rates"
|
||||
msgstr "Kurse"
|
||||
|
||||
msgctxt "field:investment.asset,name:"
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
msgctxt "field:investment.asset,rate:"
|
||||
msgid "Current Rate"
|
||||
msgstr "aktueller Kurs"
|
||||
|
||||
msgctxt "field:investment.asset,updtsource:"
|
||||
msgid "Update Source"
|
||||
msgstr "Kursquelle"
|
||||
msgctxt "field:investment.asset,date:"
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
msgctxt "help:investment.asset,updtsource:"
|
||||
msgid "Select a source for the course update."
|
||||
msgstr "Wählen Sie eine Quelle für die Kursaktualisierung aus."
|
||||
msgctxt "help:investment.asset,date:"
|
||||
msgid "Date of current rate"
|
||||
msgstr "Datum des aktuellen Kurses"
|
||||
|
||||
msgctxt "field:investment.asset,updtsources:"
|
||||
msgid "Update Sources"
|
||||
msgstr "Kursquellen"
|
||||
|
||||
msgctxt "help:investment.asset,updtsources:"
|
||||
msgid "Select sources for the course update. The course sources are tried until a valid value has been read."
|
||||
msgstr "Wählen Sie Quellen für die Kursaktualisierung aus. Die Kursquellen werden probiert bis ein gültiger Wert gelesen wurde."
|
||||
|
||||
msgctxt "field:investment.asset,updttime:"
|
||||
msgid "Time"
|
||||
msgstr "Zeitpunkt"
|
||||
|
||||
msgctxt "field:investment.asset,updtneeded:"
|
||||
msgid "Course update needed"
|
||||
msgstr "Kursaktualisierung nötig"
|
||||
msgctxt "field:investment.asset,nextupdate:"
|
||||
msgid "Next Update"
|
||||
msgstr "nächste Aktualisierung"
|
||||
|
||||
msgctxt "field:investment.asset,change_day1:"
|
||||
msgid "Previous Day"
|
||||
msgstr "Vortag"
|
||||
|
||||
msgctxt "help:investment.asset,change_day1:"
|
||||
msgid "percentage change in value compared to the previous day"
|
||||
msgstr "prozentuale Wertänderung zum Vortag"
|
||||
|
||||
msgctxt "field:investment.asset,change_month1:"
|
||||
msgid "1 Month"
|
||||
msgstr "1 Monat"
|
||||
|
||||
msgctxt "help:investment.asset,change_month1:"
|
||||
msgid "percentage change in value compared to last month"
|
||||
msgstr "prozentuale Wertänderung zum letzten Monat"
|
||||
|
||||
msgctxt "field:investment.asset,change_month3:"
|
||||
msgid "3 Months"
|
||||
msgstr "3 Monate"
|
||||
|
||||
msgctxt "help:investment.asset,change_month3:"
|
||||
msgid "percentage change in value during 3 months"
|
||||
msgstr "Prozentuale Wertänderung während 3 Monate"
|
||||
|
||||
msgctxt "field:investment.asset,change_month6:"
|
||||
msgid "6 Months"
|
||||
msgstr "6 Monate"
|
||||
|
||||
msgctxt "help:investment.asset,change_month6:"
|
||||
msgid "percentage change in value during 6 months"
|
||||
msgstr "Prozentuale Wertänderung während 6 Monate"
|
||||
|
||||
msgctxt "field:investment.asset,change_month12:"
|
||||
msgid "1 Year"
|
||||
msgstr "1 Jahr"
|
||||
|
||||
msgctxt "help:investment.asset,change_month12:"
|
||||
msgid "percentage change in value during 1 year"
|
||||
msgstr "Prozentuale Wertänderung während 1 Jahr"
|
||||
|
||||
msgctxt "field:investment.asset,change_symbol:"
|
||||
msgid "Symbol"
|
||||
msgstr "Symbol"
|
||||
|
||||
msgctxt "field:investment.asset,updtdays:"
|
||||
msgid "Select days"
|
||||
msgstr "Tage wählen"
|
||||
|
||||
msgctxt "selection:investment.asset,updtdays:"
|
||||
msgid "Mon - Fri"
|
||||
msgstr "Mo - Fr"
|
||||
|
||||
msgctxt "selection:investment.asset,updtdays:"
|
||||
msgid "Mon - Sun"
|
||||
msgstr "Mo - So"
|
||||
|
||||
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 #
|
||||
###############################
|
||||
msgctxt "model:investment.asset_source_rel,name:"
|
||||
msgid "Asset Source Relation"
|
||||
msgstr "Vermögenswert Quelle Verknüpfung"
|
||||
|
||||
msgctxt "field:investment.asset_source_rel,source:"
|
||||
msgid "Source"
|
||||
msgstr "Quelle"
|
||||
|
||||
msgctxt "field:investment.asset_source_rel,asset:"
|
||||
msgid "Asset"
|
||||
msgstr "Vermögenswert"
|
||||
|
||||
|
||||
#####################
|
||||
|
@ -226,6 +410,10 @@ msgctxt "view:investment.source:"
|
|||
msgid "Purely javascript-based websites do not work here."
|
||||
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:"
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
@ -354,6 +542,22 @@ msgctxt "help:investment.source,fndident:"
|
|||
msgid "Identifier found during test query."
|
||||
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 #
|
||||
|
@ -393,3 +597,15 @@ msgstr "Wertpapierkennnummer (WKN)"
|
|||
msgctxt "selection:product.identifier,type:"
|
||||
msgid "Stock market symbol"
|
||||
msgstr "Börsensymbol"
|
||||
|
||||
|
||||
####################
|
||||
# diagram.graphdef #
|
||||
####################
|
||||
msgctxt "view:diagram.graphdef:"
|
||||
msgid "Asset"
|
||||
msgstr "Vermögenswert"
|
||||
|
||||
msgctxt "field:diagram.graphdef,asset:"
|
||||
msgid "Asset"
|
||||
msgstr "Vermögenswert"
|
||||
|
|
234
locale/en.po
234
locale/en.po
|
@ -22,6 +22,26 @@ msgctxt "model:ir.message,text:msg_currency_rate_positive"
|
|||
msgid "A asset rate must be positive."
|
||||
msgstr "A asset rate must be positive."
|
||||
|
||||
msgctxt "model:ir.message,text:msg_import_err_date"
|
||||
msgid "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)"
|
||||
msgstr "failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)"
|
||||
|
||||
msgctxt "model:ir.message,text:msg_err_unknown_content"
|
||||
msgid "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"
|
||||
msgid "Investment"
|
||||
msgstr "Investment"
|
||||
|
@ -46,6 +66,58 @@ msgctxt "model:ir.action,name:updt_source_wiz"
|
|||
msgid "Update Source"
|
||||
msgstr "Update Source"
|
||||
|
||||
msgctxt "model:ir.action,name:act_import_wizard"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "Import CSV-File"
|
||||
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_current"
|
||||
msgid "Current"
|
||||
msgstr "Current"
|
||||
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_inactive"
|
||||
msgid "Inactive"
|
||||
msgstr "Inactive"
|
||||
|
||||
msgctxt "model:ir.action.act_window.domain,name:act_asset_domain_all"
|
||||
msgid "All"
|
||||
msgstr "All"
|
||||
|
||||
msgctxt "model:investment.imp_wiz.start,name:"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "Import CSV-File"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,asset:"
|
||||
msgid "Asset"
|
||||
msgstr "Asset"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,file_:"
|
||||
msgid "CSV-File"
|
||||
msgstr "CSV-File"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,dec_divider:"
|
||||
msgid "Decimal divider"
|
||||
msgstr "Decimal divider"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,date_fmt:"
|
||||
msgid "Date format"
|
||||
msgstr "Date format"
|
||||
|
||||
msgctxt "field:investment.imp_wiz.start,field_delimiter:"
|
||||
msgid "Field delimiter"
|
||||
msgstr "Field delimiter"
|
||||
|
||||
msgctxt "model:investment.imp_wiz,name:"
|
||||
msgid "Import CSV-File"
|
||||
msgstr "Import CSV-File"
|
||||
|
||||
msgctxt "wizard_button:investment.imp_wiz,start,end:"
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
msgctxt "wizard_button:investment.imp_wiz,start,importf:"
|
||||
msgid "Import File"
|
||||
msgstr "Import File"
|
||||
|
||||
msgctxt "model:investment.source_update,name:"
|
||||
msgid "Update Source"
|
||||
msgstr "Update Source"
|
||||
|
@ -58,6 +130,10 @@ msgctxt "view:investment.asset:"
|
|||
msgid "Currency and Units"
|
||||
msgstr "Currency and Units"
|
||||
|
||||
msgctxt "view:investment.asset:"
|
||||
msgid "Gain and Loss"
|
||||
msgstr "Gain and Loss"
|
||||
|
||||
msgctxt "view:investment.asset:"
|
||||
msgid "Identifiers"
|
||||
msgstr "Identifiers"
|
||||
|
@ -74,21 +150,13 @@ msgctxt "field:investment.asset,company:"
|
|||
msgid "Company"
|
||||
msgstr "Company"
|
||||
|
||||
msgctxt "field:investment.asset,company_currency:"
|
||||
msgid "Company Currency"
|
||||
msgstr "Company Currency"
|
||||
|
||||
msgctxt "field:investment.asset,company_currency_digits:"
|
||||
msgid "Currency Digits (Ref.)"
|
||||
msgstr "Currency Digits (Ref.)"
|
||||
|
||||
msgctxt "field:investment.asset,currency:"
|
||||
msgid "Currency"
|
||||
msgstr "Currency"
|
||||
|
||||
msgctxt "field:investment.asset,currency_digits:"
|
||||
msgid "Currency Digits"
|
||||
msgstr "Currency Digits"
|
||||
msgid "Digits"
|
||||
msgstr "Digits"
|
||||
|
||||
msgctxt "field:investment.asset,product:"
|
||||
msgid "Product"
|
||||
|
@ -106,6 +174,14 @@ msgctxt "field:investment.asset,uom:"
|
|||
msgid "UOM"
|
||||
msgstr "UOM"
|
||||
|
||||
msgctxt "field:investment.asset,symbol:"
|
||||
msgid "UOM"
|
||||
msgstr "UOM"
|
||||
|
||||
msgctxt "field:investment.asset,asset_symbol:"
|
||||
msgid "Symbol"
|
||||
msgstr "Symbol"
|
||||
|
||||
msgctxt "field:investment.asset,wkn:"
|
||||
msgid "NSIN"
|
||||
msgstr "NSIN"
|
||||
|
@ -134,25 +210,117 @@ msgctxt "field:investment.asset,rates:"
|
|||
msgid "Rates"
|
||||
msgstr "Rates"
|
||||
|
||||
msgctxt "field:investment.asset,name:"
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
msgctxt "field:investment.asset,rate:"
|
||||
msgid "Current Rate"
|
||||
msgstr "Current Rate"
|
||||
|
||||
msgctxt "field:investment.asset,updtsource:"
|
||||
msgid "Update Source"
|
||||
msgstr "Update Source"
|
||||
msgctxt "field:investment.asset,date:"
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
msgctxt "help:investment.asset,updtsource:"
|
||||
msgid "Select a source for the course update."
|
||||
msgstr "Select a source for the course update."
|
||||
msgctxt "help:investment.asset,date:"
|
||||
msgid "Date of current rate"
|
||||
msgstr "Date of current rate"
|
||||
|
||||
msgctxt "field:investment.asset,updtsources:"
|
||||
msgid "Update Sources"
|
||||
msgstr "Update Sources"
|
||||
|
||||
msgctxt "help:investment.asset,updtsources:"
|
||||
msgid "Select sources for the course update. The course sources are tried until a valid value has been read."
|
||||
msgstr "Select sources for the course update. The course sources are tried until a valid value has been read."
|
||||
|
||||
msgctxt "field:investment.asset,updttime:"
|
||||
msgid "Time"
|
||||
msgstr "Time"
|
||||
|
||||
msgctxt "field:investment.asset,updtneeded:"
|
||||
msgid "Course update needed"
|
||||
msgstr "Course update needed"
|
||||
msgctxt "field:investment.asset,nextupdate:"
|
||||
msgid "Next Update"
|
||||
msgstr "Next Update"
|
||||
|
||||
msgctxt "field:investment.asset,change_day1:"
|
||||
msgid "Previous Day"
|
||||
msgstr "Previous Day"
|
||||
|
||||
msgctxt "help:investment.asset,change_day1:"
|
||||
msgid "percentage change in value compared to the previous day"
|
||||
msgstr "percentage change in value compared to the previous day"
|
||||
|
||||
msgctxt "field:investment.asset,change_month1:"
|
||||
msgid "1 Month"
|
||||
msgstr "1 Month"
|
||||
|
||||
msgctxt "help:investment.asset,change_month1:"
|
||||
msgid "percentage change in value compared to last month"
|
||||
msgstr "percentage change in value compared to last month"
|
||||
|
||||
msgctxt "field:investment.asset,change_month3:"
|
||||
msgid "3 Months"
|
||||
msgstr "3 Months"
|
||||
|
||||
msgctxt "help:investment.asset,change_month3:"
|
||||
msgid "percentage change in value during 3 months"
|
||||
msgstr "percentage change in value during 3 months"
|
||||
|
||||
msgctxt "field:investment.asset,change_month6:"
|
||||
msgid "6 Months"
|
||||
msgstr "6 Months"
|
||||
|
||||
msgctxt "help:investment.asset,change_month6:"
|
||||
msgid "percentage change in value during 6 months"
|
||||
msgstr "percentage change in value during 6 months"
|
||||
|
||||
msgctxt "field:investment.asset,change_month12:"
|
||||
msgid "1 Year"
|
||||
msgstr "1 Year"
|
||||
|
||||
msgctxt "help:investment.asset,change_month12:"
|
||||
msgid "percentage change in value during 1 year"
|
||||
msgstr "percentage change in value during 1 year"
|
||||
|
||||
msgctxt "field:investment.asset,change_symbol:"
|
||||
msgid "Symbol"
|
||||
msgstr "Symbol"
|
||||
|
||||
msgctxt "field:investment.asset,updtdays:"
|
||||
msgid "Select days"
|
||||
msgstr "Select days"
|
||||
|
||||
msgctxt "selection:investment.asset,updtdays:"
|
||||
msgid "Mon - Fri"
|
||||
msgstr "Mon - Fri"
|
||||
|
||||
msgctxt "selection:investment.asset,updtdays:"
|
||||
msgid "Mon - Sun"
|
||||
msgstr "Mon - Sun"
|
||||
|
||||
msgctxt "field:investment.asset,updturl:"
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
msgctxt "help:investment.asset,updturl:"
|
||||
msgid "URL for data retrieval."
|
||||
msgstr "URL for data retrieval."
|
||||
|
||||
msgctxt "field:investment.asset,updturl_enable:"
|
||||
msgid "URL required"
|
||||
msgstr "URL required"
|
||||
|
||||
msgctxt "model:investment.asset_source_rel,name:"
|
||||
msgid "Asset Source Relation"
|
||||
msgstr "Asset Source Relation"
|
||||
|
||||
msgctxt "field:investment.asset_source_rel,source:"
|
||||
msgid "Source"
|
||||
msgstr "Source"
|
||||
|
||||
msgctxt "field:investment.asset_source_rel,asset:"
|
||||
msgid "Asset"
|
||||
msgstr "Asset"
|
||||
|
||||
msgctxt "model:investment.source,name:"
|
||||
msgid "Online Source"
|
||||
|
@ -194,6 +362,10 @@ msgctxt "view:investment.source:"
|
|||
msgid "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:"
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
@ -322,6 +494,22 @@ msgctxt "help:investment.source,fndident:"
|
|||
msgid "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:"
|
||||
msgid "Rate"
|
||||
msgstr "Rate"
|
||||
|
@ -350,3 +538,11 @@ msgctxt "selection:product.identifier,type:"
|
|||
msgid "National Securities Identifying Number (NSIN)"
|
||||
msgstr "National Securities Identifying Number (NSIN)"
|
||||
|
||||
msgctxt "selection:product.identifier,type:"
|
||||
msgid "Stock market symbol"
|
||||
msgstr "Stock market symbol"
|
||||
|
||||
msgctxt "view:diagram.graphdef:"
|
||||
msgid "Asset"
|
||||
msgstr "Asset"
|
||||
|
||||
|
|
6
menu.xml
6
menu.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
@ -19,7 +19,7 @@ full copyright notices and license terms. -->
|
|||
|
||||
<!-- menu: /Investment/Online Source -->
|
||||
<menuitem id="menu_onlinesource" action="act_source_view" sequence="10"
|
||||
icon="tryton-list" parent="menu_investment"/>
|
||||
icon="mds-stockonline" parent="menu_investment"/>
|
||||
<record model="ir.ui.menu-res.group" id="menu_onlinesource-group_investment_admin">
|
||||
<field name="menu" ref="menu_onlinesource"/>
|
||||
<field name="group" ref="group_investment_admin"/>
|
||||
|
@ -27,7 +27,7 @@ full copyright notices and license terms. -->
|
|||
|
||||
<!-- menu: /Investment/Asset -->
|
||||
<menuitem id="menu_asset" action="act_asset_view" sequence="20"
|
||||
icon="tryton-list" parent="menu_investment"/>
|
||||
icon="mds-asset" parent="menu_investment"/>
|
||||
<record model="ir.ui.menu-res.group" id="menu_asset-group_investment">
|
||||
<field name="menu" ref="menu_asset"/>
|
||||
<field name="group" ref="group_investment"/>
|
||||
|
|
17
message.xml
17
message.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
@ -11,6 +11,21 @@ full copyright notices and license terms. -->
|
|||
<record model="ir.message" id="msg_rate_positive">
|
||||
<field name="text">A asset rate must be positive.</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_import_err_date">
|
||||
<field name="text">failed to read column %(colnr)s of file, expected date (format: %(datefmt)s)</field>
|
||||
</record>
|
||||
<record model="ir.message" id="msg_err_unknown_content">
|
||||
<field name="text">failed to identify row content: %(linetxt)s</field>
|
||||
</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>
|
||||
</tryton>
|
||||
|
|
349
onlinesource.py
349
onlinesource.py
|
@ -1,16 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from string import Template
|
||||
import requests, logging, html2text, re
|
||||
import requests
|
||||
import logging
|
||||
import html2text
|
||||
import re
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique, Check
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.model import ModelView, ModelSQL, fields
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.i18n import gettext
|
||||
from trytond.exceptions import UserError
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -28,63 +32,115 @@ sel_rgxidtype = [
|
|||
|
||||
sel_rgxdatefmt = [
|
||||
('%d.%m.%Y', 'dd.mm.yyyy'),
|
||||
('%d.%m.%y', 'dd.mm.yy'),
|
||||
('%m/%d/%Y', 'mm/dd/yyyy'),
|
||||
('%m/%d/%y', 'mm/dd/yy'),
|
||||
('%Y-%m-%d', 'yyyy-mm-dd'),
|
||||
('%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']
|
||||
|
||||
|
||||
STATES_WEB = {
|
||||
'invisible': Eval('query_method', '') != 'web',
|
||||
'required': Eval('query_method', '') == 'web',
|
||||
}
|
||||
DEPENDS_WEB = ['query_method']
|
||||
|
||||
|
||||
class OnlineSource(ModelSQL, ModelView):
|
||||
'Online Source'
|
||||
__name__ = 'investment.source'
|
||||
|
||||
name = fields.Char(string='Name', required=True)
|
||||
url = fields.Char(string='URL', required=True)
|
||||
nohtml = fields.Boolean(string='Remove HTML',
|
||||
help='Removes HTML tags before the text is interpreted.')
|
||||
rgxdate = fields.Char(string='Date', required=True,
|
||||
help='Regex code to find the date in the downloaded HTML file.')
|
||||
rgxdatefmt = fields.Selection(string='Date format', required=True,
|
||||
selection=sel_rgxdatefmt)
|
||||
rgxrate = fields.Char(string='Rate', required=True,
|
||||
help='Regex code to find the rate in the downloaded HTML file.')
|
||||
rgxdecimal = fields.Selection(string='Decimal Separator', required=True,
|
||||
help='Decimal separator for converting the market value into a number.',
|
||||
selection=sel_rgxdecimal)
|
||||
rgxident = fields.Char(string='Identifier',
|
||||
help='Regex code to find the identifier in the downloaded HTML file.')
|
||||
rgxidtype = fields.Selection(string='ID-Type', selection=sel_rgxidtype,
|
||||
query_method = fields.Selection(
|
||||
string='Method', required=True,
|
||||
help='Select the method to retrieve the data.',
|
||||
selection='get_query_methods')
|
||||
url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB)
|
||||
fixed_url = fields.Boolean(
|
||||
string='Fixed URL',
|
||||
states={'invisible': Eval('query_method', '') != 'web'},
|
||||
depends=DEPENDS_WEB,
|
||||
help='URL must be defined at investment record.')
|
||||
nohtml = fields.Boolean(
|
||||
string='Remove HTML',
|
||||
help='Removes HTML tags before the text is interpreted.',
|
||||
states={'invisible': STATES_WEB['invisible']},
|
||||
depends=DEPENDS_WEB)
|
||||
rgxdate = fields.Char(
|
||||
string='Date',
|
||||
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.',
|
||||
states={
|
||||
'required': Bool(Eval('rgxident', '')),
|
||||
}, depends=['rgxident'])
|
||||
'invisible': STATES_WEB['invisible'],
|
||||
}, depends=DEPENDS_WEB+['rgxident'])
|
||||
|
||||
# field to test requests
|
||||
used_url = fields.Function(fields.Char(string='Used URL', readonly=True,
|
||||
help='This URL is used to retrieve the HTML file.'),
|
||||
used_url = fields.Function(fields.Char(
|
||||
string='Used URL', readonly=True,
|
||||
help='This URL is used to retrieve the HTML file.',
|
||||
states={'invisible': STATES_WEB['invisible']}, depends=DEPENDS_WEB),
|
||||
'on_change_with_used_url')
|
||||
nsin = fields.Function(fields.Char(string='NSIN'),
|
||||
'on_change_with_nsin', setter='set_test_value')
|
||||
isin = fields.Function(fields.Char(string='ISIN'),
|
||||
'on_change_with_isin', setter='set_test_value')
|
||||
symbol = fields.Function(fields.Char(string='Symbol'),
|
||||
'on_change_with_symbol', setter='set_test_value')
|
||||
http_state = fields.Function(fields.Char(string='HTTP-State',
|
||||
nsin = fields.Function(fields.Char(
|
||||
string='NSIN'), 'on_change_with_nsin', setter='set_test_value')
|
||||
isin = fields.Function(fields.Char(
|
||||
string='ISIN'), 'on_change_with_isin', setter='set_test_value')
|
||||
symbol = fields.Function(fields.Char(
|
||||
string='Symbol'), 'on_change_with_symbol', setter='set_test_value')
|
||||
http_state = fields.Function(fields.Char(
|
||||
string='HTTP-State',
|
||||
readonly=True), 'on_change_with_http_state')
|
||||
text = fields.Function(fields.Text(string='Result',
|
||||
readonly=True), 'on_change_with_text')
|
||||
fnddate = fields.Function(fields.Date(string='Date', readonly=True,
|
||||
text = fields.Function(fields.Text(
|
||||
string='Result', readonly=True), 'on_change_with_text')
|
||||
fnddate = fields.Function(fields.Date(
|
||||
string='Date', readonly=True,
|
||||
help='Date found during test query.'),
|
||||
'on_change_with_fnddate')
|
||||
fndrate = fields.Function(fields.Numeric(string='Rate', readonly=True,
|
||||
help='Rate found during test query.', digits=(16,4)),
|
||||
fndrate = fields.Function(fields.Numeric(
|
||||
string='Rate', readonly=True,
|
||||
help='Rate found during test query.', digits=(16, 4)),
|
||||
'on_change_with_fndrate')
|
||||
fndident = fields.Function(fields.Char(string='Identifier', readonly=True,
|
||||
fndident = fields.Function(fields.Char(
|
||||
string='Identifier', readonly=True,
|
||||
help='Identifier found during test query.'),
|
||||
'on_change_with_fndident')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(OnlineSource, cls).__setup__()
|
||||
cls._order.insert(0, ('name', 'DESC'))
|
||||
|
||||
@classmethod
|
||||
def default_query_method(cls):
|
||||
""" default: web
|
||||
"""
|
||||
return 'web'
|
||||
|
||||
@classmethod
|
||||
def default_url(cls):
|
||||
""" defaul-url
|
||||
|
@ -127,6 +183,12 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
"""
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def default_fixed_url(cls):
|
||||
""" default: False
|
||||
"""
|
||||
return False
|
||||
|
||||
@fields.depends(*fields_check)
|
||||
def on_change_nsin(self):
|
||||
""" run request
|
||||
|
@ -169,16 +231,22 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
def on_change_with_symbol(self, name=None):
|
||||
return ''
|
||||
|
||||
@fields.depends('url', 'isin', 'nsin', 'symbol')
|
||||
@fields.depends('url', 'isin', 'nsin', 'symbol', 'fixed_url')
|
||||
def on_change_with_used_url(self, name=None):
|
||||
""" get url for testing
|
||||
"""
|
||||
if self.url:
|
||||
return self.get_url_with_parameter(
|
||||
isin = self.isin,
|
||||
nsin = self.nsin,
|
||||
symbol = self.symbol,
|
||||
)
|
||||
isin=self.isin,
|
||||
nsin=self.nsin,
|
||||
symbol=self.symbol,
|
||||
url=self.url)
|
||||
|
||||
@classmethod
|
||||
def get_query_methods(cls):
|
||||
""" get list of query-methods
|
||||
"""
|
||||
return [('web', gettext('investment.msg_querytype_web'))]
|
||||
|
||||
@classmethod
|
||||
def set_test_value(cls, record, name, value):
|
||||
|
@ -186,34 +254,69 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
"""
|
||||
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):
|
||||
""" use updated values to call online-source,
|
||||
for testing parameters
|
||||
"""
|
||||
OSourc = Pool().get('investment.source')
|
||||
|
||||
result = OSourc.read_from_website(
|
||||
self,
|
||||
isin = self.isin,
|
||||
nsin = self.nsin,
|
||||
symbol = self.symbol,
|
||||
debug = True,
|
||||
)
|
||||
self.text = result.get('text', None)
|
||||
self.http_state = result.get('http_state', None)
|
||||
self.fnddate = result.get('date', None)
|
||||
self.fndrate = result.get('rate', None)
|
||||
self.fndident = result.get('code', None)
|
||||
result = OSourc.run_query_method(
|
||||
self, self.isin, self.nsin, self.url,
|
||||
self.symbol, debug=True)
|
||||
if result:
|
||||
self.text = result.get('text', None)
|
||||
self.http_state = result.get('http_state', None)
|
||||
self.fnddate = result.get('date', None)
|
||||
self.fndrate = result.get('rate', None)
|
||||
self.fndident = result.get('code', None)
|
||||
|
||||
def get_url_with_parameter(self, isin=None, nsin=None, symbol=None):
|
||||
def get_url_with_parameter(
|
||||
self, isin=None, nsin=None, symbol=None, url=None):
|
||||
""" generate url
|
||||
"""
|
||||
if self.fixed_url is True:
|
||||
if not url:
|
||||
raise UserError(gettext(
|
||||
'investment.msg_missing_url',
|
||||
oname=self.rec_name))
|
||||
return url
|
||||
|
||||
if self.url:
|
||||
return Template(self.url).substitute({
|
||||
'isin': isin if isin is not None else '',
|
||||
'nsin': nsin if nsin is not None else '',
|
||||
'symbol': symbol if symbol is not None else '',
|
||||
})
|
||||
'isin': isin if isin is not None else '',
|
||||
'nsin': nsin if nsin is not None else '',
|
||||
'symbol': symbol if symbol is not None else ''})
|
||||
|
||||
@classmethod
|
||||
def update_rate(cls, asset):
|
||||
|
@ -221,62 +324,77 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
"""
|
||||
pool = Pool()
|
||||
Rate = pool.get('investment.rate')
|
||||
IrDate = pool.get('ir.date')
|
||||
|
||||
if asset.updtsource is None:
|
||||
if len(asset.updtsources) == 0:
|
||||
return
|
||||
rate_data = cls.read_from_website(
|
||||
asset.updtsource,
|
||||
isin = asset.isin,
|
||||
nsin = asset.wkn,
|
||||
symbol = asset.secsymb,
|
||||
)
|
||||
|
||||
if len(asset.updtsource.rgxident or '') > 0:
|
||||
# check result - same code?
|
||||
code = rate_data.get('code', None)
|
||||
if code:
|
||||
asset_code = getattr(asset, {
|
||||
'isin': 'isin',
|
||||
'nsin': 'wkn',
|
||||
'symbol': 'secsymb',
|
||||
}[asset.updtsource.rgxidtype])
|
||||
if (asset_code or '').lower() != code.lower():
|
||||
# fail
|
||||
logger.warning(
|
||||
'update_rate: got wrong code "%(wrong)s" - expected "%(exp)s"' % {
|
||||
'exp': asset_code,
|
||||
'wrong': code,
|
||||
})
|
||||
return False
|
||||
for updtsource in asset.updtsources:
|
||||
rate_data = cls.run_query_method(
|
||||
updtsource,
|
||||
isin=asset.isin,
|
||||
nsin=asset.wkn,
|
||||
symbol=asset.secsymb,
|
||||
url=asset.updturl)
|
||||
|
||||
to_create = {
|
||||
'date': rate_data.get('date', None),
|
||||
'rate': rate_data.get('rate', None),
|
||||
'asset': asset.id,
|
||||
}
|
||||
if (to_create['date'] is not None) and \
|
||||
(to_create['rate'] is not None):
|
||||
# check if exists
|
||||
if Rate.search_count([
|
||||
('asset.id', '=', asset.id),
|
||||
('date', '=', to_create['date']),
|
||||
]) == 0:
|
||||
Rate.create([to_create])
|
||||
return True
|
||||
if len(updtsource.rgxident or '') > 0:
|
||||
# check result - same code?
|
||||
code = rate_data.get('code', None)
|
||||
if code:
|
||||
asset_code = getattr(asset, {
|
||||
'isin': 'isin',
|
||||
'nsin': 'wkn',
|
||||
'symbol': 'secsymb',
|
||||
}[updtsource.rgxidtype])
|
||||
if (asset_code or '').lower() != code.lower():
|
||||
# fail
|
||||
logger.warning(
|
||||
'update_rate: got wrong code ' +
|
||||
'"%(wrong)s" - expected "%(exp)s"' % {
|
||||
'exp': asset_code,
|
||||
'wrong': code})
|
||||
continue
|
||||
|
||||
to_create = {
|
||||
'date': rate_data.get('date', None),
|
||||
'rate': rate_data.get('rate', None),
|
||||
'asset': asset.id}
|
||||
if (to_create['date'] is not None) and \
|
||||
(to_create['rate'] is not None):
|
||||
# check if exists
|
||||
if Rate.search_count([
|
||||
('asset.id', '=', asset.id),
|
||||
('date', '=', to_create['date'])]) == 0:
|
||||
Rate.create([to_create])
|
||||
return True
|
||||
else:
|
||||
# if we got a record for today - stop
|
||||
# otherwise try next source
|
||||
if to_create['date'] == IrDate.today():
|
||||
break
|
||||
return False
|
||||
|
||||
def get_regex_result(self, html_text, field_name):
|
||||
""" run regex on html-text, convert result
|
||||
"""
|
||||
OSource = Pool().get('investment.source')
|
||||
|
||||
rgxcode = getattr(self, field_name) or ''
|
||||
if len(rgxcode) == 0:
|
||||
return None
|
||||
|
||||
search_result = re.compile(rgxcode).search(html_text)
|
||||
if search_result is None:
|
||||
return None
|
||||
try:
|
||||
search_result = re.compile(rgxcode).search(html_text)
|
||||
if search_result is None:
|
||||
return None
|
||||
except Exception as e1:
|
||||
raise UserError(gettext(
|
||||
'investment.msg_bug_in_regexquery',
|
||||
errmsg=str(e1),
|
||||
fname=getattr(OSource, field_name).string,
|
||||
code=rgxcode))
|
||||
|
||||
try :
|
||||
try:
|
||||
result = search_result.group(1)
|
||||
except IndexError:
|
||||
result = search_result.group(0)
|
||||
|
@ -285,20 +403,23 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
dec_sep = [',', '.']
|
||||
dec_sep.remove(self.rgxdecimal)
|
||||
|
||||
result = result.replace(dec_sep[0], '').replace(self.rgxdecimal, '.')
|
||||
try :
|
||||
result = result.replace(
|
||||
dec_sep[0], '').replace(self.rgxdecimal, '.')
|
||||
try:
|
||||
result = Decimal(result)
|
||||
except :
|
||||
except Exception:
|
||||
result = None
|
||||
elif field_name == 'rgxdate':
|
||||
try :
|
||||
try:
|
||||
result = datetime.strptime(result, self.rgxdatefmt).date()
|
||||
except :
|
||||
except Exception:
|
||||
result = None
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def read_from_website(cls, updtsource, isin=None, nsin=None, symbol=None, debug=False):
|
||||
def read_from_website(
|
||||
cls, updtsource, isin=None, nsin=None,
|
||||
symbol=None, url=None, debug=False):
|
||||
""" read from url, extract values
|
||||
"""
|
||||
result = {}
|
||||
|
@ -309,10 +430,10 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
|
||||
res1 = requests.get(
|
||||
updtsource.get_url_with_parameter(
|
||||
isin = isin,
|
||||
nsin = nsin,
|
||||
symbol = symbol,
|
||||
),
|
||||
isin=isin,
|
||||
nsin=nsin,
|
||||
symbol=symbol,
|
||||
url=url),
|
||||
allow_redirects=True,
|
||||
timeout=5.0)
|
||||
|
||||
|
@ -321,7 +442,7 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
'msg': res1.reason,
|
||||
}
|
||||
|
||||
if res1.status_code in [200, 204]:
|
||||
if res1.status_code in [200, 204, 410]:
|
||||
html = res1.text
|
||||
|
||||
# remove html-tags
|
||||
|
@ -341,8 +462,10 @@ class OnlineSource(ModelSQL, ModelView):
|
|||
result['rate'] = updtsource.get_regex_result(html, 'rgxrate')
|
||||
result['date'] = updtsource.get_regex_result(html, 'rgxdate')
|
||||
result['code'] = updtsource.get_regex_result(html, 'rgxident')
|
||||
else :
|
||||
logger.error('read_from_website: %(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
|
||||
else:
|
||||
logger.error(
|
||||
'read_from_website: ' +
|
||||
'%(code)s, url: %(url)s, redirects: [%(redirects)s]' % {
|
||||
'code': res1.status_code,
|
||||
'url': res1.url,
|
||||
'redirects': ', '.join([x.url for x in res1.history]),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
|
104
rate.py
104
rate.py
|
@ -1,34 +1,37 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
from trytond.model import ModelView, ModelSQL, fields, Unique, Check
|
||||
from trytond.model import (
|
||||
ModelView, ModelSQL, fields, Unique, Check, SymbolMixin, Index)
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval, Bool
|
||||
from trytond.pyson import Eval
|
||||
|
||||
|
||||
class Rate(ModelSQL, ModelView):
|
||||
class Rate(SymbolMixin, ModelSQL, ModelView):
|
||||
'Rate'
|
||||
__name__ = 'investment.rate'
|
||||
|
||||
asset = fields.Many2One(string='Asset', required=True,
|
||||
select=True, ondelete='CASCADE',
|
||||
asset = fields.Many2One(
|
||||
string='Asset', required=True, ondelete='CASCADE',
|
||||
model_name='investment.asset')
|
||||
date = fields.Date(string='Date', required=True, select=True)
|
||||
rate = fields.Numeric(string='Rate', required=True,
|
||||
digits=(16, Eval('asset_digits', 4)),
|
||||
depends=['asset_digits'])
|
||||
date = fields.Date(string='Date', required=True)
|
||||
rate = fields.Numeric(
|
||||
string='Rate', required=True,
|
||||
digits=(16, Eval('asset_digits', 4)), depends=['asset_digits'])
|
||||
|
||||
asset_digits = fields.Function(fields.Integer(string='Digits',
|
||||
readonly=True), 'on_change_with_asset_digits')
|
||||
currency = fields.Function(fields.Many2One(string='Currency',
|
||||
readonly=True, model_name='currency.currency'),
|
||||
'on_change_with_currency')
|
||||
uom = fields.Function(fields.Many2One(string='Uom',
|
||||
readonly=True, model_name='product.uom'),
|
||||
'on_change_with_uom')
|
||||
asset_digits = fields.Function(fields.Integer(
|
||||
string='Digits', readonly=True), 'get_rate_data')
|
||||
currency = fields.Function(fields.Many2One(
|
||||
string='Currency', readonly=True, model_name='currency.currency'),
|
||||
'get_rate_data')
|
||||
uom = fields.Function(fields.Many2One(
|
||||
string='Uom', readonly=True, model_name='product.uom'),
|
||||
'get_rate_data')
|
||||
symbol = fields.Function(fields.Char(
|
||||
string='Symbol', readonly=True), 'get_rate_data')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
|
@ -43,6 +46,21 @@ class Rate(ModelSQL, ModelView):
|
|||
'currency.msg_rate_positive'),
|
||||
]
|
||||
cls._order.insert(0, ('date', 'DESC'))
|
||||
cls._sql_indexes.update({
|
||||
Index(
|
||||
t,
|
||||
(t.date, Index.Range(order='DESC'))),
|
||||
Index(
|
||||
t,
|
||||
(t.rate, Index.Range())),
|
||||
Index(
|
||||
t,
|
||||
(t.asset, Index.Equality())),
|
||||
Index(
|
||||
t,
|
||||
(t.asset, Index.Equality()),
|
||||
(t.date, Index.Range(order='DESC'))),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def default_date(cls):
|
||||
|
@ -51,26 +69,40 @@ class Rate(ModelSQL, ModelView):
|
|||
IrDate = Pool().get('ir.date')
|
||||
return IrDate.today()
|
||||
|
||||
@fields.depends('asset', '_parent_asset.uom')
|
||||
def on_change_with_uom(self, name=None):
|
||||
""" get unit of asset
|
||||
@classmethod
|
||||
def get_rate_data(cls, rates, names):
|
||||
""" speed up: get values for rate
|
||||
"""
|
||||
if self.asset:
|
||||
return self.asset.uom.id
|
||||
pool = Pool()
|
||||
Asset = pool.get('investment.asset')
|
||||
tab_asset = Asset.__table__()
|
||||
tab_rate = cls.__table__()
|
||||
cursor = Transaction().connection.cursor()
|
||||
|
||||
@fields.depends('asset', '_parent_asset.currency')
|
||||
def on_change_with_currency(self, name=None):
|
||||
""" get currency
|
||||
"""
|
||||
if self.asset:
|
||||
return self.asset.currency.id
|
||||
query = tab_asset.join(
|
||||
tab_rate,
|
||||
condition=tab_asset.id == tab_rate.asset,
|
||||
).select(
|
||||
tab_rate.id,
|
||||
tab_asset.uom,
|
||||
tab_asset.currency,
|
||||
tab_asset.currency_digits,
|
||||
where=tab_rate.id.in_([x.id for x in rates]),
|
||||
)
|
||||
cursor.execute(*query)
|
||||
records = cursor.fetchall()
|
||||
|
||||
@fields.depends('asset', '_parent_asset.currency_digits')
|
||||
def on_change_with_asset_digits(self, name=None):
|
||||
""" get digits for asset
|
||||
"""
|
||||
if self.asset:
|
||||
return self.asset.currency_digits
|
||||
return 4
|
||||
result = {x: {y.id: None for y in rates} for x in names}
|
||||
for record in records:
|
||||
r1 = {
|
||||
'symbol': '%',
|
||||
'uom': record[1],
|
||||
'currency': record[2],
|
||||
'asset_digits': record[3],
|
||||
}
|
||||
|
||||
for n in names:
|
||||
result[n][record[0]] = r1[n]
|
||||
return result
|
||||
|
||||
# Rate
|
||||
|
|
2
rate.xml
2
rate.xml
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
|
2
scripts/__init__.py
Normal file
2
scripts/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
176
scripts/import_investment_historicalrates.py
Normal file
176
scripts/import_investment_historicalrates.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the currency_ecbrate-module from m-ds for Tryton.
|
||||
# The COPYRIGHT file at the top level of this repository contains the
|
||||
# full copyright notices and license terms.
|
||||
|
||||
import csv, os, sys
|
||||
from argparse import ArgumentParser
|
||||
from datetime import datetime, date
|
||||
from decimal import Decimal
|
||||
|
||||
|
||||
try:
|
||||
from proteus import Model, config
|
||||
except ImportError:
|
||||
prog = os.path.basename(sys.argv[0])
|
||||
sys.exit("proteus must be installed to use %s" % prog)
|
||||
|
||||
|
||||
def read_csv_file(file_name, dec_devider, date_fmt, delimiter):
|
||||
""" read file from csv
|
||||
"""
|
||||
result = []
|
||||
|
||||
del_chars = ['.', ',']
|
||||
del_chars.remove(dec_devider)
|
||||
min_date = None
|
||||
max_date = None
|
||||
min_rate = None
|
||||
max_rate = None
|
||||
|
||||
with open(file_name, 'r', encoding='latin1') as fhdl:
|
||||
csv_lines = csv.DictReader(fhdl, dialect='excel', delimiter=delimiter)
|
||||
|
||||
for line in csv_lines:
|
||||
try :
|
||||
date_val = datetime.strptime(line.get('date', None).strip(), date_fmt).date()
|
||||
except :
|
||||
raise ValueError('- failed to read column 1 of file, expected date (format: %s)' % date_fmt)
|
||||
|
||||
try :
|
||||
rate_val = line.get('rate', None).replace(del_chars[0], '').strip()
|
||||
rate_val = Decimal(rate_val.replace(dec_devider, '.'))
|
||||
except :
|
||||
raise ValueError('- failed to read column 1 of file, expected date (format: %s)' % date_fmt)
|
||||
|
||||
if isinstance(date_val, date) and isinstance(rate_val, Decimal):
|
||||
result.append({'date': date_val, 'rate': rate_val})
|
||||
|
||||
# date range
|
||||
if max_date is None:
|
||||
max_date = date_val
|
||||
else :
|
||||
if max_date < date_val:
|
||||
max_date = date_val
|
||||
|
||||
if min_date is None:
|
||||
min_date = date_val
|
||||
else :
|
||||
if min_date > date_val:
|
||||
min_date = date_val
|
||||
|
||||
# rate range
|
||||
if max_rate is None:
|
||||
max_rate = rate_val
|
||||
else :
|
||||
if max_rate < rate_val:
|
||||
max_rate = rate_val
|
||||
|
||||
if min_rate is None:
|
||||
min_rate = rate_val
|
||||
else :
|
||||
if min_rate > rate_val:
|
||||
min_rate = rate_val
|
||||
|
||||
else :
|
||||
raise ValueError('- failed to identify row content: %s' % line)
|
||||
|
||||
print('- found %d records' % len(result))
|
||||
print('- dates from %s to %s' % (
|
||||
min_date.isoformat() if min_date is not None else '-',
|
||||
max_date.isoformat() if max_date is not None else '-',
|
||||
))
|
||||
print('- rates from %s to %s' % (
|
||||
str(min_rate) if min_rate is not None else '-',
|
||||
str(max_rate) if max_rate is not None else '-',
|
||||
))
|
||||
return (result, max_date, min_date)
|
||||
|
||||
|
||||
|
||||
def upload_rates(isin, rates_list, max_date, min_date):
|
||||
""" generate to_create for rates
|
||||
"""
|
||||
Rate = Model.get('investment.rate')
|
||||
Asset = Model.get('investment.asset')
|
||||
|
||||
# get id of asset by isin
|
||||
assets = Asset.find([
|
||||
('isin', '=', isin),
|
||||
])
|
||||
if len(assets) == 0:
|
||||
print('- ISIN %s not found' % isin)
|
||||
return
|
||||
|
||||
# get rate in date-range
|
||||
rates = Rate.find([
|
||||
('asset.id', '=', assets[0].id),
|
||||
('date', '>=', min_date),
|
||||
('date', '<=', max_date),
|
||||
])
|
||||
existing_dates = [x.date for x in rates]
|
||||
done_dates = []
|
||||
|
||||
to_create = []
|
||||
for rate in rates_list:
|
||||
if rate['date'] in existing_dates:
|
||||
continue
|
||||
if rate['date'] in done_dates:
|
||||
continue
|
||||
|
||||
to_create.append({
|
||||
'asset': assets[0].id,
|
||||
'date': rate['date'],
|
||||
'rate': rate['rate'],
|
||||
})
|
||||
done_dates.append(rate['date'])
|
||||
|
||||
if len(to_create) > 0:
|
||||
print('- upload %d historical rates...' % len(to_create))
|
||||
Rate.create(to_create, context={})
|
||||
print('- finished upload')
|
||||
else :
|
||||
print('- nothing to upload')
|
||||
|
||||
|
||||
def do_import(csv_file, isin, dec_devider, date_fmt, delimiter):
|
||||
""" run import
|
||||
"""
|
||||
print('\n--== Import historical asset rates ==--')
|
||||
print('- file: %s' % csv_file)
|
||||
print('- ISIN: %s' % isin)
|
||||
print('- date-format: %s, decimal divider: "%s", delimiter: "%s"' % (date_fmt, dec_devider, delimiter))
|
||||
(lines, max_date, min_date) = read_csv_file(csv_file, dec_devider, date_fmt, delimiter)
|
||||
upload_rates(isin, lines, max_date, min_date)
|
||||
|
||||
print('--== finish import ==--')
|
||||
|
||||
|
||||
def main(database, config_file, csv_file, dec_devider, date_fmt, isin, delimiter):
|
||||
config.set_trytond(database, config_file=config_file)
|
||||
with config.get_config().set_context(active_test=False):
|
||||
do_import(csv_file, isin, dec_devider, date_fmt, delimiter)
|
||||
|
||||
|
||||
def run():
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('-d', '--database', dest='database', required=True)
|
||||
parser.add_argument('-c', '--config', dest='config_file', help='the trytond config file')
|
||||
parser.add_argument('-f', '--file', dest='csv_file', required=True,
|
||||
help='CSV-file to import, should contain two columns: 1. date, 2. numeric, first line must have "date" and "rate"')
|
||||
parser.add_argument('-p', '--decimal', default=',', dest='decimal_divider',
|
||||
help='decimal divider, defaults to: ,')
|
||||
parser.add_argument('-t', '--delimiter', default=';', dest='delimiter',
|
||||
help='field delimiter for csv-table, defaults to: ;')
|
||||
parser.add_argument('-a', '--dateformat', default='%d.%m.%Y', dest='date_format',
|
||||
help='date format like %%d.%%m.%%Y or %%Y-%%m-%%d or similiar')
|
||||
parser.add_argument('-i', '--isin', dest='isin', required=True, help='ISIN of the target asset')
|
||||
|
||||
args = parser.parse_args()
|
||||
main(args.database, args.config_file, args.csv_file, args.decimal_divider, \
|
||||
args.date_format, args.isin, args.delimiter)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
75
setup.py
75
setup.py
|
@ -2,7 +2,7 @@
|
|||
"""
|
||||
|
||||
# Always prefer setuptools over distutils
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import setup
|
||||
# To use a consistent encoding
|
||||
from codecs import open
|
||||
from os import path
|
||||
|
@ -22,7 +22,7 @@ with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
|||
|
||||
# tryton.cfg einlesen
|
||||
config = ConfigParser()
|
||||
config.readfp(open('tryton.cfg'))
|
||||
config.read_file(open('tryton.cfg'))
|
||||
info = dict(config.items('tryton'))
|
||||
for key in ('depends', 'extras_depend', 'xml'):
|
||||
if key in info:
|
||||
|
@ -36,10 +36,10 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
|
|||
l2 = i.strip().split(';')
|
||||
if len(l2) < 4:
|
||||
continue
|
||||
modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]}
|
||||
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
|
||||
|
||||
# tryton-version
|
||||
major_version = 6
|
||||
major_version = 7
|
||||
minor_version = 0
|
||||
|
||||
requires = ['requests>=2.26', 'html2text']
|
||||
|
@ -51,42 +51,47 @@ for dep in info.get('depends', []):
|
|||
prefix = modversion[dep]['prefix']
|
||||
|
||||
if len(modversion[dep]['max']) > 0:
|
||||
requires.append('%s_%s >= %s, <= %s' %
|
||||
(prefix, dep, modversion[dep]['min'], modversion[dep]['max']))
|
||||
else :
|
||||
requires.append('%s_%s >= %s' %
|
||||
(prefix, dep, modversion[dep]['min']))
|
||||
else :
|
||||
requires.append('%s_%s >= %s.%s, < %s.%s' %
|
||||
('trytond', dep, major_version, minor_version,
|
||||
requires.append('%s_%s >= %s, <= %s' % (
|
||||
prefix, dep, modversion[dep]['min'],
|
||||
modversion[dep]['max']))
|
||||
else:
|
||||
requires.append('%s_%s >= %s' % (
|
||||
prefix, dep, modversion[dep]['min']))
|
||||
else:
|
||||
requires.append('%s_%s >= %s.%s, < %s.%s' % (
|
||||
'trytond', dep, major_version, minor_version,
|
||||
major_version, minor_version + 1))
|
||||
requires.append('trytond >= %s.%s, < %s.%s' %
|
||||
(major_version, minor_version, major_version, minor_version + 1))
|
||||
requires.append('trytond >= %s.%s, < %s.%s' % (
|
||||
major_version, minor_version, major_version, minor_version + 1))
|
||||
|
||||
setup(name='%s_%s' % (PREFIX, MODULE),
|
||||
setup(
|
||||
name='%s_%s' % (PREFIX, MODULE),
|
||||
version=info.get('version', '0.0.1'),
|
||||
description='Tryton module to add investment items.',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/x-rst',
|
||||
url='https://www.m-ds.de/',
|
||||
download_url='https://scmdev.m-ds.de/Tryton/Extra/investment',
|
||||
author='martin-data services',
|
||||
author_email='service@m-ds.de',
|
||||
license='GPL-3',
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
'Framework :: Tryton',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Customer Service',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Financial and Insurance Industry',
|
||||
'Topic :: Office/Business',
|
||||
'Topic :: Office/Business :: Financial :: Accounting',
|
||||
'Natural Language :: German',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Plugins',
|
||||
'Framework :: Tryton',
|
||||
'Intended Audience :: Developers',
|
||||
'Intended Audience :: Customer Service',
|
||||
'Intended Audience :: Information Technology',
|
||||
'Intended Audience :: Financial and Insurance Industry',
|
||||
'Topic :: Office/Business',
|
||||
'Topic :: Office/Business :: Financial :: Accounting',
|
||||
'Natural Language :: German',
|
||||
'Natural Language :: English',
|
||||
'Operating System :: OS Independent',
|
||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],
|
||||
|
||||
keywords='tryton investment shares commodities',
|
||||
|
@ -95,10 +100,10 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
|||
'trytond.modules.%s' % MODULE,
|
||||
],
|
||||
package_data={
|
||||
'trytond.modules.%s' % MODULE: (info.get('xml', [])
|
||||
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
|
||||
'view/*.xml', 'icon/*.svg',
|
||||
'versiondep.txt', 'README.rst']),
|
||||
'trytond.modules.%s' % MODULE: (info.get('xml', []) + [
|
||||
'tryton.cfg', 'locale/*.po', 'tests/*.py',
|
||||
'view/*.xml', 'icon/*.svg', 'scripts/*.py',
|
||||
'versiondep.txt', 'README.rst']),
|
||||
},
|
||||
|
||||
install_requires=requires,
|
||||
|
@ -106,5 +111,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
|
|||
entry_points="""
|
||||
[trytond.modules]
|
||||
%s = trytond.modules.%s
|
||||
[console_scripts]
|
||||
trytond_import_investment_historicalrates = trytond.modules.investment.scripts.import_investment_historicalrates:run [data]
|
||||
""" % (MODULE, MODULE),
|
||||
)
|
||||
|
|
|
@ -1,21 +1,95 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
<data>
|
||||
|
||||
<record model="investment.source" id="web_finanzen_net">
|
||||
<field name="name">www.finanzen.net - ETF</field>
|
||||
<field name="name">www.finanzen.net - ETF (Tradegate)</field>
|
||||
<field name="url">https://www.finanzen.net/etf/${isin}/tgt</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\nKurszeit (\d+\.\d+\.\d+) \d{2}:\d{2}:\d{2}.*\n</field>
|
||||
<field name="rgxdate">\nKurszeit\s+(\d+\.\d+\.\d+)\s+\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="rgxrate">\nKurs\s+(\d+,\d+)\s+EUR.*\n</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
<field name="rgxident">WKN:.* ISIN:\s+([A-Z,0-9]+).*</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_finanzen_net_stu">
|
||||
<field name="name">www.finanzen.net - ETF (Stuttgard)</field>
|
||||
<field name="url">https://www.finanzen.net/etf/${isin}/stu</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\nKurszeit\s+(\d+\.\d+\.\d+)\s+\d{2}:\d{2}:\d{2}.*\n</field>
|
||||
<field name="rgxdatefmt">%d.%m.%Y</field>
|
||||
<field name="rgxrate">\nKurs\s+(\d+,\d+)\s+EUR.*\n</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
<field name="rgxident">WKN:.* ISIN: ([A-Z,0-9]+).*</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_finanzen_fonds">
|
||||
<field name="name">www.finanzen.net - Fonds</field>
|
||||
<field name="url">https://www.finanzen.net/fonds/${isin}/tgt</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\n\*\*Kursdatum\*\*\s+(\d+\.\d+\.\d+).*\n</field>
|
||||
<field name="rgxdatefmt">%d.%m.%Y</field>
|
||||
<field name="rgxrate">\n\*\*Kurs\*\*\s+(\d+,\d+)\s+EUR.*\n</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
<field name="rgxident">WKN:.*\s+ISIN:\s+([A-Z,0-9]+).*</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_finanzen_rohstoffe">
|
||||
<field name="name">www.finanzen.net - Rohstoffe</field>
|
||||
<field name="url">https://www.finanzen.net/rohstoffe/${symbol}</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\nKurszeit\s+(\d+\.\d+\.\d+)\s+\d+:\d+:\d+.*\n</field>
|
||||
<field name="rgxdatefmt">%d.%m.%Y</field>
|
||||
<field name="rgxrate">\nKurs\s+([\d+\.]*\d+,\d+)\s+USD\s*\n</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_finanzen_aktie">
|
||||
<field name="name">www.finanzen.net - Aktie</field>
|
||||
<field name="url">https://www.finanzen.net/aktien/${symbol}</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\nKurszeit(\d+\.\d+\.\d+) \d{2}:\d{2}:\d{2}.*\n</field>
|
||||
<field name="rgxdatefmt">%d.%m.%Y</field>
|
||||
<field name="rgxrate">\nKurs(\d+,\d+) EUR.*\n</field>
|
||||
<field name="rgxident">\nISIN([A-Z,0-9]+).*\n</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_markets_ft_com">
|
||||
<field name="name">Financial Times UK</field>
|
||||
<field name="url">https://markets.ft.com/data/etfs/tearsheet/summary?s=${symbol}</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">Data delayed at least 15 minutes, as of (.*) \d+:\d+ GMT\.</field>
|
||||
<field name="rgxdatefmt">%b %d %Y</field>
|
||||
<field name="rgxrate">Price\D+([\d,]*\d+\.\d+)</field>
|
||||
<field name="rgxdecimal">.</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_sbroker">
|
||||
<field name="name">www.sbroker.de</field>
|
||||
<field name="url">https://www.sbroker.de/sbl/mdaten_analyse/dksuche_a?SEARCH_VALUE=${isin}</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="rgxdate">\nDatum\s*(?:\/ Uhrzeit)*: (\d+\.\d+\.\d+)\s*(?:\/ \d+:\d+)*\s*\n</field>
|
||||
<field name="rgxdatefmt">%d.%m.%y</field>
|
||||
<field name="rgxrate">(?:Kurs aktuell|Rucknahmepreis)\s+[()\w\./\[\]!]+\s+(\d+,\d+)\s+(EUR|€)</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
</record>
|
||||
<record model="investment.source" id="web_faz">
|
||||
<field name="name">FAZ</field>
|
||||
<field name="url">https://www.faz.net/aktuell/finanzen/kurs/etf/</field>
|
||||
<field name="nohtml" eval="True"/>
|
||||
<field name="fixed_url" eval="True"/>
|
||||
<field name="rgxdate">(\d{2}\.\d{2}\.\d{4})</field>
|
||||
<field name="rgxdatefmt">%d.%m.%y</field>
|
||||
<field name="rgxrate">\nKurs(\d+.\d+)\s+€\s+\n</field>
|
||||
<field name="rgxdecimal">,</field>
|
||||
<field name="rgxident">\nWKN:.*\sISIN: ([A-Z,0-9]+).*\n</field>
|
||||
<field name="rgxidtype">isin</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</tryton>
|
||||
|
|
|
@ -1,28 +1,2 @@
|
|||
# This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
# this repository contains the full copyright notices and license terms.
|
||||
|
||||
import trytond.tests.test_tryton
|
||||
import unittest
|
||||
|
||||
from trytond.modules.investment.tests.test_asset import AssetTestCase
|
||||
from trytond.modules.investment.tests.test_rate import RateTestCase
|
||||
from trytond.modules.investment.tests.test_source import SourceTestCase
|
||||
|
||||
|
||||
__all__ = ['suite']
|
||||
|
||||
|
||||
class InvestmentTestCase(\
|
||||
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,326 +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
|
||||
|
||||
|
||||
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.rec_name,
|
||||
))
|
||||
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
|
||||
"""
|
||||
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)
|
||||
|
||||
@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(asset.updtsource, None)
|
||||
self.assertEqual(asset.updttime, None)
|
||||
|
||||
asset.updtsource = o_source
|
||||
asset.updttime = time(10, 45)
|
||||
asset.save()
|
||||
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
|
||||
self.assertEqual(asset.updttime, time(10, 45))
|
||||
|
||||
asset.updtsource = None
|
||||
asset.on_change_updtsource()
|
||||
self.assertEqual(asset.updtsource, None)
|
||||
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],
|
||||
{
|
||||
'updtsource': o_source.id,
|
||||
'updttime': time(10, 45),
|
||||
}])
|
||||
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
|
||||
self.assertEqual(asset.updttime, time(10, 45))
|
||||
self.assertEqual(len(asset.rates), 0)
|
||||
|
||||
with Transaction().set_context({
|
||||
'qdate': date(2022, 10, 15),
|
||||
'qtime': time(10, 30),
|
||||
}):
|
||||
# no rates exists - wait for 10:45
|
||||
self.assertEqual(asset.updtneeded, True)
|
||||
self.assertEqual(
|
||||
Asset.search_count([('updtneeded', '=', True)]),
|
||||
0)
|
||||
|
||||
with Transaction().set_context({
|
||||
'qdate': date(2022, 10, 15),
|
||||
'qtime': time(10, 46),
|
||||
}):
|
||||
# no rates exists - run at 10:46
|
||||
self.assertEqual(asset.updtneeded, True)
|
||||
self.assertEqual(
|
||||
Asset.search_count([('updtneeded', '=', True)]),
|
||||
1)
|
||||
|
||||
# add rate at yesterday
|
||||
Asset.write(*[
|
||||
[asset],
|
||||
{
|
||||
'rates': [('create', [{
|
||||
'date': date(2022, 10, 14),
|
||||
'rate': Decimal('1.5'),
|
||||
}])],
|
||||
}])
|
||||
self.assertEqual(len(asset.rates), 1)
|
||||
|
||||
with Transaction().set_context({
|
||||
'qdate': date(2022, 10, 15),
|
||||
'qtime': time(10, 30),
|
||||
}):
|
||||
# 1x rate exists - run at 10:30
|
||||
self.assertEqual(asset.updtneeded, True)
|
||||
self.assertEqual(
|
||||
Asset.search_count([('updtneeded', '=', True)]),
|
||||
0)
|
||||
|
||||
with Transaction().set_context({
|
||||
'qdate': date(2022, 10, 15),
|
||||
'qtime': time(10, 46),
|
||||
}):
|
||||
# 1x rate exists yesterday - run at 10:46
|
||||
self.assertEqual(asset.updtneeded, True)
|
||||
self.assertEqual(
|
||||
Asset.search_count([('updtneeded', '=', True)]),
|
||||
1)
|
||||
|
||||
# add rate at today
|
||||
Asset.write(*[
|
||||
[asset],
|
||||
{
|
||||
'rates': [('create', [{
|
||||
'date': date(2022, 10, 15),
|
||||
'rate': Decimal('1.5'),
|
||||
}])],
|
||||
}])
|
||||
self.assertEqual(len(asset.rates), 2)
|
||||
|
||||
with Transaction().set_context({
|
||||
'qdate': date(2022, 10, 15),
|
||||
'qtime': time(10, 47),
|
||||
}):
|
||||
# 1x rate exists today - run at 10:47
|
||||
self.assertEqual(asset.updtneeded, True)
|
||||
self.assertEqual(
|
||||
Asset.search_count([('updtneeded', '=', True)]),
|
||||
0)
|
||||
|
||||
@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([
|
||||
('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')
|
||||
|
||||
@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,50 +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')
|
||||
|
||||
# end RateTestCase
|
|
@ -1,128 +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
|
||||
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],
|
||||
{
|
||||
'updtsource': osource.id,
|
||||
}])
|
||||
self.assertEqual(asset.wkn, '965515')
|
||||
self.assertEqual(asset.isin, 'XC0009655157')
|
||||
self.assertEqual(asset.secsymb, '1472977')
|
||||
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
|
||||
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
|
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,11 +1,13 @@
|
|||
[tryton]
|
||||
version=6.0.0
|
||||
version=7.0.0
|
||||
depends:
|
||||
ir
|
||||
res
|
||||
company
|
||||
currency
|
||||
product
|
||||
extras_depends:
|
||||
diagram
|
||||
xml:
|
||||
icon.xml
|
||||
message.xml
|
||||
|
@ -15,5 +17,7 @@ xml:
|
|||
sources_def.xml
|
||||
update_wiz.xml
|
||||
rate.xml
|
||||
diagram.xml
|
||||
import_wiz.xml
|
||||
menu.xml
|
||||
cron.xml
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# This file is part of the investment-module from m-ds for Tryton.
|
||||
# This file is part of the investment-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.
|
||||
|
||||
|
@ -24,8 +24,14 @@ class UpdateSoureWizard(Wizard):
|
|||
context = Transaction().context
|
||||
|
||||
assets = Asset.browse(context.get('active_ids', []))
|
||||
to_run_activities = []
|
||||
for asset in assets:
|
||||
OnlineSource.update_rate(asset)
|
||||
if OnlineSource.update_rate(asset):
|
||||
to_run_activities.append(asset)
|
||||
|
||||
if to_run_activities:
|
||||
Asset.after_update_actions(to_run_activities)
|
||||
|
||||
return 'end'
|
||||
|
||||
# UpdateSoureWizard
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!-- This file is part of the investment-module from m-ds for Tryton.
|
||||
<!-- This file is part of the investment-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. -->
|
||||
<tryton>
|
||||
|
|
|
@ -2,24 +2,46 @@
|
|||
<!-- 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. -->
|
||||
<form col="4">
|
||||
<form col="6">
|
||||
<label name="product" />
|
||||
<field name="product" />
|
||||
<field name="product" colspan="3"/>
|
||||
<label name="rate" />
|
||||
<field name="rate" symbol="currency"/>
|
||||
<field name="rate" symbol="asset_symbol"/>
|
||||
|
||||
<separator id="sepunits" colspan="4" string="Currency and Units"/>
|
||||
<label id="labdate" string=" "/>
|
||||
<label name="nextupdate" colspan="2"/>
|
||||
<field name="nextupdate"/>
|
||||
<label name="date" />
|
||||
<field name="date"/>
|
||||
|
||||
<separator id="sepperc" colspan="6" string="Gain and Loss"/>
|
||||
<label name="change_day1" />
|
||||
<field name="change_day1" symbol="change_symbol"/>
|
||||
<label name="change_month1" />
|
||||
<field name="change_month1" symbol="change_symbol"/>
|
||||
<label name="change_month3" />
|
||||
<field name="change_month3" symbol="change_symbol"/>
|
||||
|
||||
<label name="change_month6" />
|
||||
<field name="change_month6" symbol="change_symbol"/>
|
||||
<label name="change_month12" />
|
||||
<field name="change_month12" symbol="change_symbol"/>
|
||||
<newline/>
|
||||
|
||||
<separator id="sepunits" colspan="6" string="Currency and Units"/>
|
||||
<label name="currency" />
|
||||
<field name="currency" />
|
||||
<label name="currency_digits" />
|
||||
<field name="currency_digits" />
|
||||
<newline/>
|
||||
|
||||
<label name="uom" />
|
||||
<field name="uom" />
|
||||
<label name="product_uom" />
|
||||
<field name="product_uom" />
|
||||
<newline/>
|
||||
|
||||
<notebook>
|
||||
<notebook colspan="6">
|
||||
<page id="pgids" col="4" string="Identifiers">
|
||||
<label name="wkn" />
|
||||
<field name="wkn" />
|
||||
|
@ -33,10 +55,17 @@ full copyright notices and license terms. -->
|
|||
<field name="rates" mode="tree,form,graph"/>
|
||||
</page>
|
||||
<page id="pgupdate" col="4" string="Course Update">
|
||||
<label name="updtsource"/>
|
||||
<field name="updtsource"/>
|
||||
<label name="updttime"/>
|
||||
<field name="updttime"/>
|
||||
<label name="updtdays"/>
|
||||
<field name="updtdays"/>
|
||||
|
||||
<label name="updturl"/>
|
||||
<field name="updturl" colspan="3"/>
|
||||
<field name="updturl_enable"/>
|
||||
<newline/>
|
||||
|
||||
<field name="updtsources" colspan="4"/>
|
||||
</page>
|
||||
</notebook>
|
||||
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
The COPYRIGHT file at the top level of this repository contains the
|
||||
full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="rec_name"/>
|
||||
<field name="isin"/>
|
||||
<field name="secsymb"/>
|
||||
<field name="wkn"/>
|
||||
<field name="rate"/>
|
||||
<field name="currency"/>
|
||||
<field name="uom"/>
|
||||
<field name="name" expand="1"/>
|
||||
<field name="change_day1" optional="0"/>
|
||||
<field name="change_month1" optional="0"/>
|
||||
<field name="change_month3" optional="0"/>
|
||||
<field name="change_month6" optional="0"/>
|
||||
<field name="date" optional="0"/>
|
||||
<field name="rate" symbol="asset_symbol" optional="0"/>
|
||||
<field name="isin" optional="0"/>
|
||||
<field name="wkn" optional="0"/>
|
||||
</tree>
|
||||
|
|
19
view/import_start_form.xml
Normal file
19
view/import_start_form.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?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. -->
|
||||
<form col="4">
|
||||
<label name="asset"/>
|
||||
<field name="asset" colspan="3"/>
|
||||
|
||||
<label name="dec_divider"/>
|
||||
<field name="dec_divider"/>
|
||||
<label name="date_fmt"/>
|
||||
<field name="date_fmt"/>
|
||||
<label name="field_delimiter"/>
|
||||
<field name="field_delimiter"/>
|
||||
<newline/>
|
||||
|
||||
<label name="file_"/>
|
||||
<field name="file_"/>
|
||||
</form>
|
|
@ -5,16 +5,24 @@ full copyright notices and license terms. -->
|
|||
<form col="6">
|
||||
<label 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"/>
|
||||
<field name="url" colspan="3"/>
|
||||
|
||||
<label 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"/>
|
||||
<field name="rgxdate"/>
|
||||
<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. -->
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="query_method"/>
|
||||
<field name="url"/>
|
||||
</tree>
|
||||
|
|
Loading…
Reference in a new issue