Compare commits

...

109 commits
main ... 6.0

Author SHA1 Message Date
Frederik Jaeckel
1ce8ea145d Etikett ver 6.0.25 zum Änderungssatz 3ffad77b32f4 hinzugefügt 2023-12-01 13:48:05 +01:00
Frederik Jaeckel
66fcc5483d Version 6.0.25 2023-12-01 13:47:54 +01:00
Frederik Jaeckel
34fa28eeb9 formatting 2023-12-01 13:29:46 +01:00
Frederik Jaeckel
1a55e9fa7e asset-list: remove percent-symbol to speed up list view 2023-06-23 21:40:55 +02:00
Frederik Jaeckel
2354c4cf4f diagram: simplify queries 2023-06-23 16:41:39 +02:00
Frederik Jaeckel
b672efcd6d asset: avoid exceptions in get_identifiers() 2023-06-23 16:29:10 +02:00
Frederik Jaeckel
5573f26e9e asset/rate: speed up percent-queries 2023-06-23 16:02:31 +02:00
Frederik Jaeckel
8f226427d6 asset: fixed possible exceptions 2023-06-22 17:28:58 +02:00
Frederik Jaeckel
43f7f7ed0f Etikett ver 6.0.24 zum Änderungssatz f82310901890 hinzugefügt 2023-06-07 22:05:23 +02:00
Frederik Jaeckel
826b8b7213 Version 6.0.24 2023-06-07 22:05:11 +02:00
Frederik Jaeckel
548212553f remove: graph_form.xml 2023-06-07 21:57:27 +02:00
Frederik Jaeckel
95997ea512 test: add more context 2023-06-07 20:06:36 +02:00
Frederik Jaeckel
7d2e4c510a test: add context to product-create 2023-06-07 19:24:43 +02:00
Frederik Jaeckel
0275ee09e9 formatting 2023-06-07 18:44:53 +02:00
Frederik Jaeckel
b28723cfb1 asset/onlinesource: add fixed url,
pre defned online sources: add FAZ.net
2023-04-21 16:55:51 +02:00
Frederik Jaeckel
2166da5509 online source: formatting 2023-04-21 15:40:07 +02:00
Frederik Jaeckel
0a143e8768 add online-source www.finanzen.net - Aktie 2023-04-21 15:31:10 +02:00
Frederik Jaeckel
3a60020eac setup.py 2023-02-28 09:13:29 +01:00
Frederik Jaeckel
9df76e8e59 readme updated 2023-02-28 09:05:52 +01:00
Frederik Jaeckel
491efcc773 Etikett ver 6.0.23 zum Änderungssatz 22f3326f9eb9 hinzugefügt 2023-01-21 18:57:59 +01:00
Frederik Jaeckel
b9bcfc1f29 Version 6.0.23 2023-01-21 18:57:51 +01:00
Frederik Jaeckel
c6c0cb4ce4 removed unused imports 2023-01-13 13:48:50 +01:00
Frederik Jaeckel
73dac7b33c OnlineSource: Query method prepared for external extension 2023-01-13 13:08:52 +01:00
Frederik Jaeckel
4bb7641959 Etikett ver 6.0.22 zum Änderungssatz 0c434d333c3b hinzugefügt 2023-01-10 19:56:08 +01:00
Frederik Jaeckel
7087c151ab Version 6.0.22 2023-01-10 19:55:57 +01:00
Frederik Jaeckel
89f19ceae5 remove caching, optimize rate/query 2023-01-10 19:54:32 +01:00
Frederik Jaeckel
5eec999d41 asset: cachezeit verkürzt 2023-01-09 22:21:03 +01:00
Frederik Jaeckel
2fb2bef8b4 Etikett ver 6.0.21 zum Änderungssatz fb85e01774bb hinzugefügt 2023-01-08 21:30:39 +01:00
Frederik Jaeckel
03d0ce0a5b Version 6.0.21 2023-01-08 21:30:29 +01:00
Frederik Jaeckel
c18b07def1 caching für change_day1/month1/... 2023-01-08 20:56:27 +01:00
Frederik Jaeckel
a4242421f0 Etikett ver 6.0.20 zum Änderungssatz 49cc6bde6e18 hinzugefügt 2023-01-07 16:28:00 +01:00
Frederik Jaeckel
c9330c7195 Version 6.0.20 2023-01-07 16:27:52 +01:00
Frederik Jaeckel
25fd69be0c asset: abfragezeit für prozente optimiert 2023-01-07 16:26:22 +01:00
Frederik Jaeckel
9064ac2980 Etikett ver 6.0.19 zum Änderungssatz 5b6929d467a8 hinzugefügt 2023-01-05 22:27:44 +01:00
Frederik Jaeckel
23238b8419 Version 6.0.19 2023-01-05 22:27:34 +01:00
Frederik Jaeckel
11e9134dc9 rate: optimize for speed 2023-01-05 22:25:16 +01:00
Frederik Jaeckel
9d461b6d76 Etikett ver 6.0.18 zum Änderungssatz e413671670d3 hinzugefügt 2023-01-04 20:23:26 +01:00
Frederik Jaeckel
06fdbace50 Version 6.0.18 2023-01-04 20:23:18 +01:00
Frederik Jaeckel
553aa80acd asset: Feld 'change_symbol' für Prozenzzeichen + test 2023-01-04 20:20:27 +01:00
Frederik Jaeckel
c9a6df2371 asset: spalte 'einheit' entfällt, Spalte 'Kurs' hat nun die Einheit 2023-01-04 17:12:46 +01:00
Frederik Jaeckel
92cd89ef2e quellen korrigiert 2022-12-24 12:38:10 +01:00
Frederik Jaeckel
4dde704e57 setup - read_file 2022-12-20 12:31:36 +01:00
Frederik Jaeckel
0e8dc38abb Etikett ver 6.0.17 zum Änderungssatz ac30badd3d0d hinzugefügt 2022-12-19 21:58:29 +01:00
Frederik Jaeckel
f9b4f12055 Version 6.0.17 2022-12-19 21:58:18 +01:00
Frederik Jaeckel
76ea68489c test für wizard 2022-12-19 21:53:00 +01:00
Frederik Jaeckel
ed6dae7d59 import wizard 2022-12-19 21:13:32 +01:00
Frederik Jaeckel
d76951d2f2 Etikett ver 6.0.16 zum Änderungssatz db4466e585ae hinzugefügt 2022-12-18 13:42:04 +01:00
Frederik Jaeckel
2c38ddd357 Version 6.0.16 2022-12-18 13:41:49 +01:00
Frederik Jaeckel
f5b9929c51 onlinesource: sortierung, neues datumsformat 'dd.mm.yy',
neue quellen: finanzen.net/Stuttgard, s-broker
2022-12-18 13:40:13 +01:00
Frederik Jaeckel
46a19fe4c4 Etikett ver 6.0.15 zum Änderungssatz c51c29a66b41 hinzugefügt 2022-12-16 21:47:53 +01:00
Frederik Jaeckel
f74ee23a76 Version 6.0.15 2022-12-16 21:47:42 +01:00
Frederik Jaeckel
2c750b2253 asset: rekursion behoben, erlaubt aktion nach update 2022-12-16 14:17:28 +01:00
Frederik Jaeckel
e703c759a3 updates an asset nach online-aktion 2022-12-16 13:37:36 +01:00
Frederik Jaeckel
36e6fb52d5 Etikett ver 6.0.14 zum Änderungssatz 7403bff0289a hinzugefügt 2022-12-09 21:41:09 +01:00
Frederik Jaeckel
b8648d629b Version 6.0.14 2022-12-09 21:40:48 +01:00
Frederik Jaeckel
7dd7c9a140 asset: Feld 'symbol' in übersetzter form 2022-12-09 09:43:30 +01:00
Frederik Jaeckel
813a794db4 Etikett ver 6.0.13 zum Änderungssatz d217c90a57be hinzugefügt 2022-12-06 08:55:45 +01:00
Frederik Jaeckel
2977e5187f Version 6.0.13 2022-12-06 08:55:34 +01:00
Frederik Jaeckel
77c287fc90 asset: prozentwerte mit einheit im form 2022-12-06 08:53:40 +01:00
Frederik Jaeckel
c73584af32 asset: currency_digits limit --> 6 2022-12-05 22:34:51 +01:00
Frederik Jaeckel
6b3b44e9af asset: bereichsgrenzen für currency_digits, farbe rot für tagesnegativ 2022-12-05 21:02:17 +01:00
Frederik Jaeckel
331c62a3f6 Etikett ver 6.0.12 zum Änderungssatz 2135106770d7 hinzugefügt 2022-12-05 09:51:50 +01:00
Frederik Jaeckel
693dc175f1 Version 6.0.12 2022-12-05 09:51:43 +01:00
Frederik Jaeckel
5b6883cdde import-script: add field-delimiter 2022-12-05 09:49:59 +01:00
Frederik Jaeckel
6311d0e79e Etikett ver 6.0.11 zum Änderungssatz 51423a72fb3e hinzugefügt 2022-12-03 21:19:12 +01:00
Frederik Jaeckel
b531d51a7d Version 6.0.11 2022-12-03 21:18:59 +01:00
Frederik Jaeckel
08ca00a6b9 symbol fix 2022-12-03 00:08:55 +01:00
Frederik Jaeckel
3cc92ccecb felder symbol, name, product_uom optimiert,
felder company_currency entfernt
2022-12-02 23:29:01 +01:00
Frederik Jaeckel
c3d323714f icon 2022-12-02 14:58:54 +01:00
Frederik Jaeckel
53dfb14254 asset: liste mit reiter für aktuell/inaktiv/alle 2022-12-02 12:56:14 +01:00
Frederik Jaeckel
4ab79e4200 Etikett ver 6.0.10 zum Änderungssatz f00c0cc3b914 hinzugefügt 2022-12-01 16:57:29 +01:00
Frederik Jaeckel
528a6e5a86 Version 6.0.10 2022-12-01 16:57:21 +01:00
Frederik Jaeckel
d88703f117 asset: rec_name - einheit als währung/menge 2022-12-01 16:55:27 +01:00
Frederik Jaeckel
6241601fa7 asset: einheit als währung/menge 2022-12-01 16:30:50 +01:00
Frederik Jaeckel
0fe7df4b61 icons 2022-11-30 21:50:31 +01:00
Frederik Jaeckel
ebca4642fb Etikett ver 6.0.9 zum Änderungssatz 19c2e25edee8 hinzugefügt 2022-11-30 13:59:51 +01:00
Frederik Jaeckel
37aece3c65 Etikett ver 6.0.9 gelöscht 2022-11-30 13:59:41 +01:00
Frederik Jaeckel
5c588abe96 fix imports 2022-11-30 13:59:20 +01:00
Frederik Jaeckel
c6f2661f9b setup.py 2022-11-30 13:35:21 +01:00
Frederik Jaeckel
bda4f66b47 Etikett ver 6.0.9 zum Änderungssatz 771ac3e2f632 hinzugefügt 2022-11-30 13:33:33 +01:00
Frederik Jaeckel
d097395825 Version 6.0.9 2022-11-30 13:33:24 +01:00
Frederik Jaeckel
f1b00e376b importscript für historische kurse 2022-11-30 13:32:16 +01:00
Frederik Jaeckel
f462b11758 Etikett ver 6.0.8 zum Änderungssatz 1eeb956c7547 hinzugefügt 2022-11-29 21:56:55 +01:00
Frederik Jaeckel
d431a422e8 Version 6.0.8 2022-11-29 21:56:48 +01:00
Frederik Jaeckel
b8495231e5 asset: online-quelle als liste 2022-11-29 21:54:27 +01:00
Frederik Jaeckel
8760fd4aab Etikett ver 6.0.7 zum Änderungssatz 71846ed7e4ce hinzugefügt 2022-11-28 23:20:16 +01:00
Frederik Jaeckel
aaa143f373 Version 6.0.7 2022-11-28 23:20:07 +01:00
Frederik Jaeckel
99e18094aa diagram: interpolator ergänzt,
asset: berechnung 'nextupdate' korrigiert
2022-11-28 23:15:28 +01:00
Frederik Jaeckel
d1421403b1 asset: sortierung - neueste nach oben, farben - älter als 5 tage = gedimmt 2022-11-28 15:44:07 +01:00
Frederik Jaeckel
475457d202 Etikett ver 6.0.6 zum Änderungssatz 2ffa138f55f2 hinzugefügt 2022-11-26 22:51:13 +01:00
Frederik Jaeckel
c1cc1acb5f Version 6.0.7 2022-11-26 22:51:04 +01:00
Frederik Jaeckel
7bb3e4f929 asset:tabellenzugriff optimiert,
diagram: darstellung in diagramm ergänzt
2022-11-26 22:42:02 +01:00
Frederik Jaeckel
047b6980e4 Etikett ver 6.0.5 zum Änderungssatz 6e85642a2e97 hinzugefügt 2022-11-25 22:50:14 +01:00
Frederik Jaeckel
9d88cf1067 Version 6.0.5 2022-11-25 22:50:03 +01:00
Frederik Jaeckel
620c42fc62 asset: suche in name, sortiert nach name, sortierer für wkn, isin, symbl 2022-11-25 22:46:32 +01:00
Frederik Jaeckel
6311dce3d1 asset: spalten tag/monat/3monate... ok + test 2022-11-25 21:55:43 +01:00
Frederik Jaeckel
64b8383096 abfrage der prozentwerte für tag/1 monat, 3 monate, 6 monate, 12 monate
test muß noch
2022-11-25 15:28:49 +01:00
Frederik Jaeckel
a99d11a4a0 Etikett ver 6.0.4 zum Änderungssatz 8e3f0b006892 hinzugefügt 2022-11-25 11:02:13 +01:00
Frederik Jaeckel
4ae5601c04 Version 6.0.4 2022-11-25 11:01:55 +01:00
Frederik Jaeckel
31c76dfb48 asset: abfrage updatezeitpunkt optimiert, anzeige der vortagsprozente, farbe für zeilen 2022-11-25 11:00:03 +01:00
Frederik Jaeckel
57cb06d60e prozentwerte begonnen 2022-11-24 23:17:44 +01:00
Frederik Jaeckel
5587bfea3a Etikett ver 6.0.3 zum Änderungssatz 0a2f82baca7e hinzugefügt 2022-11-23 22:23:49 +01:00
Frederik Jaeckel
f919d9e290 Version 6.0.3 2022-11-23 22:23:42 +01:00
Frederik Jaeckel
e8614b1242 bug in updtneeded-suche, online-quellen erweitert 2022-11-23 22:22:22 +01:00
Frederik Jaeckel
a71bc0a79a Etikett ver 6.0.2 zum Änderungssatz 76feab691fd9 hinzugefügt 2022-11-23 10:45:38 +01:00
Frederik Jaeckel
f1d9b3b1dd Version 6.0.2 2022-11-23 10:45:28 +01:00
Frederik Jaeckel
4947b495c2 asset: feld 'Datum' + 'Name' neu, rec_name optimiert, Test korrigiert 2022-11-23 10:44:09 +01:00
Frederik Jaeckel
aeb949cc20 Etikett ver 6.0.1 zum Änderungssatz e16a79cd456e hinzugefügt 2022-11-22 22:47:03 +01:00
Frederik Jaeckel
916d73ef12 Version 6.0.1 2022-11-22 22:45:15 +01:00
46 changed files with 3527 additions and 962 deletions

View file

@ -11,9 +11,125 @@ Requires
========
- Tryton 6.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.25 - 01.12.2023*
- code/speed optimized
*6.0.24 - 07.06.2023*
- code optimized
*6.0.23 - 21.01.2023*
- updt: online-source optimized for external extension
*6.0.22 - 10.01.2023*
- updt: optimze rate, remove caching
*6.0.21 - 08.01.2023*
- add: caching for percentual changes
*6.0.20 - 07.01.2023*
- updt: asset - percent-values optimize for speed
*6.0.19 - 05.01.2023*
- updt: rate - optimize for speed
*6.0.18 - 04.01.2023*
- add: units to lists
- updt: online-sources
*6.0.17 - 19.12.2022*
- add: import wizard
*6.0.16 - 18.12.2022*
- add: onlinesource - sorting, sources
*6.0.15 - 16.12.2022*
- fix: recoursion
- add: enable after-update actions
*6.0.14 - 09.12.2022*
- updt: translated symbol of asset-currency
*6.0.13 - 06.12.2022*
- updt: asset-form - units for percent-values, asset-list - day-negative in red-color
*6.0.12 - 05.12.2022*
- import-script - add field-delimter to param-list
*6.0.11 - 03.12.2022*
- add: tabs for asset-list, new icons, queries optimized
*6.0.10 - 01.12.2022*
- updt: optimized asset-list, unit as symbol
*6.0.9 - 30.11.2022*
- add: script to import historical rates
*6.0.8 - 29.11.2022*
- add: online-source as list to allow multiple sources
for asset
*6.0.7 - 28.11.2022*
- fix: add diagram-interpolate
- fix: corrected nextupdate
*6.0.6 - 26.11.2022*
- add: diagram-display
*6.0.5 - 25.11.2022*
- add: sorter for name/nsin/isin/symbol
- add: columns percentage by day/month/3month/6month/12month
- fix: online-updater
*6.0.4 - 25.11.2022*
- add: assets - colors for percentual success/loss
- updt: optimize timestamp for next online-update
*6.0.3 - 23.11.2022*
- fix: bug in searcher
- add: online-sources
*6.0.2 - 23.11.2022*
- asset: add field 'date', optimized 'rec_name'
*6.0.1 - 22.11.2022*
- works
*6.0.0 - 09.11.2022*
- init

View file

@ -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')

947
asset.py

File diff suppressed because it is too large Load diff

View file

@ -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', '&gt;=', 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', '&lt;', 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
View file

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

View file

@ -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.

View file

@ -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
View 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 keyname is None:
return None
# check if query is for us
if keyname.startswith('asset'):
asset_id = int(keyname[len('asset'):])
before = Rate.search([
('date', '<', query_date),
('asset', '=', 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
View 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>

View file

@ -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>

View file

@ -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
View 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
View 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

View file

@ -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
View 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_ is not None:
(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 len(lines) > 0:
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
View 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>

View file

@ -34,6 +34,22 @@ 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."
##############
# ir.ui.menu #
@ -66,6 +82,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 +166,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 +186,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 +210,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 +246,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 +406,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 +538,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 +593,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"

View file

@ -22,6 +22,22 @@ 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.ui.menu,name:menu_investment"
msgid "Investment"
msgstr "Investment"
@ -46,6 +62,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 +126,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 +146,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 +170,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 +206,105 @@ 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 "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 +346,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 +478,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 +522,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"

View file

@ -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"/>

View file

@ -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,18 @@ 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>
</data>
</tryton>

View file

@ -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,118 @@ 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 +186,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,51 +234,89 @@ 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):
""" dont store it
"""
pass
@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 is not None:
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.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 '',
})
if self.fixed_url is True:
if url is None:
raise UserError(gettext(
'investment.msg_missing_url',
oname=self.rec_name,
))
return url
else:
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 '',
})
@classmethod
def update_rate(cls, asset):
@ -221,48 +324,57 @@ 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):
@ -276,7 +388,7 @@ class OnlineSource(ModelSQL, ModelView):
if search_result is None:
return None
try :
try:
result = search_result.group(1)
except IndexError:
result = search_result.group(0)
@ -285,20 +397,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,9 +424,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)
@ -341,8 +457,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]),

View file

@ -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>

87
rate.py
View file

@ -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)
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, select=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'])
rate = fields.Numeric(
string='Rate', required=True, select=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):
@ -51,26 +54,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

View file

@ -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
View 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.

View 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()

View file

@ -2,7 +2,7 @@
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
from setuptools import setup
# To use a consistent encoding
from codecs import open
from os import path
@ -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,7 +36,7 @@ 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
@ -51,42 +51,46 @@ 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',
],
keywords='tryton investment shares commodities',
@ -95,10 +99,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 +110,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),
)

View file

@ -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 / Uhrzeit: (\d+\.\d+\.\d+) / \d+:\d+\s+\n</field>
<field name="rgxdatefmt">%d.%m.%y</field>
<field name="rgxrate">Kurs aktuell .* (\d+,\d+)\s+EUR.*\n</field>
<field name="rgxdecimal">,</field>
<field name="rgxident">\nWKN / ISIN: [A-Z,0-9]+ / ([A-Z,0-9]+)\s+\n</field>
<field name="rgxidtype">isin</field>
</record>
<record model="investment.source" id="web_faz">
<field name="name">FAZ</field>
<field name="url">https://www.faz.net/aktuell/finanzen/kurs/etf/</field>
<field name="nohtml" eval="True"/>
<field name="fixed_url" eval="True"/>
<field name="rgxdate">(\d{2}\.\d{2}\.\d{4})</field>
<field name="rgxdatefmt">%d.%m.%y</field>
<field name="rgxrate">\nKurs(\d+.\d+)\s+€\s+\n</field>
<field name="rgxdecimal">,</field>
<field name="rgxident">\nWKN:.*\sISIN: ([A-Z,0-9]+).*\n</field>
<field name="rgxidtype">isin</field>
</record>
</data>
</tryton>

View file

@ -4,25 +4,14 @@
import trytond.tests.test_tryton
import unittest
from trytond.modules.investment.tests.test_asset import AssetTestCase
from trytond.modules.investment.tests.test_rate import RateTestCase
from trytond.modules.investment.tests.test_source import SourceTestCase
from .test_module import InvestmentTestCase
__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))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
InvestmentTestCase))
return suite

770
tests/asset.py Normal file
View file

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

50
tests/rate.py Normal file
View file

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-module from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.tests.test_tryton import with_transaction
from trytond.transaction import Transaction
from trytond.pool import Pool
from decimal import Decimal
from datetime import date
class RateTestCase(object):
""" test rate
"""
@with_transaction()
def test_rate_create(self):
""" create rate
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
with Transaction().set_context({'company': company.id}):
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(company=company, product=product)
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 1),
'rate': Decimal('2.5'),
}, {
'date': date(2022, 5, 2),
'rate': Decimal('2.8'),
}])],
}])
self.assertEqual(len(asset.rates), 2)
self.assertEqual(asset.rates[0].date, date(2022, 5, 2))
self.assertEqual(asset.rates[0].rate, Decimal('2.8'))
self.assertEqual(asset.rates[0].uom.rec_name, 'Unit')
self.assertEqual(asset.rates[0].asset_digits, 4)
self.assertEqual(asset.rates[0].currency.rec_name, 'usd')
self.assertEqual(asset.rates[0].symbol, '%')
self.assertEqual(asset.change_symbol.symbol, '%')
# end RateTestCase

135
tests/source.py Normal file
View file

@ -0,0 +1,135 @@
# -*- 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 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))
# end SourceTestCase

View file

@ -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
View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# This file is part of the investment-module from m-ds for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.tests.test_tryton import ModuleTestCase
from .asset import AssetTestCase
from .rate import RateTestCase
from .source import SourceTestCase
from .wizard import WizardTestCase
class InvestmentTestCase(
WizardTestCase,
SourceTestCase,
RateTestCase,
AssetTestCase,
ModuleTestCase):
'Test investment module'
module = 'investment'
# end InvestmentTestCase
del ModuleTestCase

View file

@ -1,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

View file

@ -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
View file

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

View file

@ -1,11 +1,13 @@
[tryton]
version=6.0.0
version=6.0.25
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

View file

@ -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 len(to_run_activities) > 0:
Asset.after_update_actions(to_run_activities)
return 'end'
# UpdateSoureWizard

View file

@ -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>

View file

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

View file

@ -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>

View file

@ -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="name" expand="1"/>
<field name="change_day1"/>
<field name="change_month1"/>
<field name="change_month3"/>
<field name="change_month6"/>
<field name="date"/>
<field name="rate" symbol="asset_symbol"/>
<field name="isin"/>
<field name="secsymb"/>
<field name="wkn"/>
<field name="rate"/>
<field name="currency"/>
<field name="uom"/>
<field name="wkn" />
</tree>

View 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>

View file

@ -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"/>

View file

@ -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>