Compare commits

...

89 commits

Author SHA1 Message Date
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
42 changed files with 2704 additions and 372 deletions

View file

@ -14,6 +14,106 @@ Requires
Changes
=======
*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,35 @@
# -*- 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')

772
asset.py
View file

@ -1,22 +1,36 @@
# -*- 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
from trytond.model import ModelView, ModelSQL, fields, SymbolMixin
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval, Bool, And
from trytond.pyson import Eval, Bool, If, Date
from trytond.report import Report
from decimal import Decimal
from datetime import time
from sql.functions import CurrentTime
from sql.conditionals import Case
from sql.functions import CurrentDate, CurrentTimestamp, Round, Extract
from sql.conditionals import Case, Coalesce, NullIf
from sql import Literal
from .diagram import Concat2
class Asset(ModelSQL, ModelView):
digits_percent = 2
sel_updtdays = [
('work', 'Mon - Fri'),
('week', 'Mon - Sun'),
]
class Asset(SymbolMixin, ModelSQL, ModelView):
'Asset'
__name__ = 'investment.asset'
name = fields.Function(fields.Char(string='Name', readonly=True),
'get_name_symbol', searcher='search_rec_name')
company = fields.Many2One(string='Company', model_name='company.company',
required=True, ondelete="RESTRICT")
product = fields.Many2One(string='Product', required=True,
@ -24,8 +38,7 @@ class Asset(ModelSQL, ModelView):
domain=[('type', '=', 'assets')])
product_uom = fields.Function(fields.Many2One(string='UOM Category',
readonly=True, model_name='product.uom.category',
help='Category of unit on the product.'),
'on_change_with_product_uom')
help='Category of unit on the product.'), 'get_name_symbol')
uom = fields.Many2One(string='UOM', required=True,
model_name='product.uom', ondelete='RESTRICT',
states={
@ -34,24 +47,28 @@ class Asset(ModelSQL, ModelView):
domain=[
('category', '=', Eval('product_uom')),
], depends=['product_uom', 'product'])
symbol = fields.Function(fields.Char(string='UOM', readonly=True),
'get_name_symbol', searcher='search_uom_symbol')
asset_symbol = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='investment.asset'),
'get_name_symbol')
rates = fields.One2Many(string='Rates', field='asset',
model_name='investment.rate')
rate = fields.Function(fields.Numeric(string='Current Rate',
readonly=True, digits=(16, Eval('currency_digits', 4)),
depends=['currency_digits']), 'on_change_with_rate')
company_currency = fields.Function(fields.Many2One(readonly=True,
string='Company Currency', states={'invisible': True},
model_name='currency.currency'),
'on_change_with_company_currency')
company_currency_digits = fields.Function(fields.Integer(
string='Currency Digits (Ref.)', readonly=True),
'on_change_with_currency_digits')
depends=['currency_digits']),
'get_rate_data', searcher='search_rate')
date = fields.Function(fields.Date(string='Date', readonly=True,
help='Date of current rate'),
'get_rate_data', searcher='search_date')
currency = fields.Many2One(string='Currency', select=True,
required=True, model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer(string='Currency Digits',
required=True)
currency_digits = fields.Integer(string='Digits', required=True,
domain=[
('currency_digits', '>=', 0),
('currency_digits', '<=', 6)])
wkn = fields.Function(fields.Char(string='NSIN', readonly=True,
help='National Securities Identifying Number'),
@ -63,16 +80,97 @@ class Asset(ModelSQL, ModelView):
help='Stock market symbol'),
'get_identifiers', searcher='search_identifier')
updtsource = fields.Many2One(string='Update Source',
help='Select a source for the course update.',
ondelete='SET NULL', model_name='investment.source')
updtsources = fields.Many2Many(string='Update Sources',
help='Select sources for the course update. The course sources are tried until a valid value has been read.',
relation_name='investment.asset_source_rel',
origin='asset', target='source')
updtdays = fields.Selection(string='Select days', required=True,
selection=sel_updtdays)
updttime = fields.Time(string='Time',
states={
'readonly': ~Bool(Eval('updtsource')),
}, depends=['updtsource'])
updtneeded = fields.Function(fields.Boolean(string='Course update needed',
'readonly': ~Bool(Eval('updtsources')),
}, depends=['updtsources'])
nextupdate = fields.Function(fields.DateTime(string='Next Update',
readonly=True),
'on_change_with_updtneeded', searcher='search_updtneeded')
'get_nextupdates', searcher='search_nextupdate')
# percentage change
change_day1 = fields.Function(fields.Numeric(string='Previous Day',
help='percentage change in value compared to the previous day',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month1 = fields.Function(fields.Numeric(string='1 Month',
help='percentage change in value compared to last month',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month3 = fields.Function(fields.Numeric(string='3 Months',
help='percentage change in value during 3 months',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month6 = fields.Function(fields.Numeric(string='6 Months',
help='percentage change in value during 6 months',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_month12 = fields.Function(fields.Numeric(string='1 Year',
help='percentage change in value during 1 year',
readonly=True, digits=(16,digits_percent)),
'get_percentage_change', searcher='search_percentage')
change_symbol = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='investment.rate'),
'get_rate_data')
@classmethod
def __register__(cls, module_name):
""" register and migrate
"""
super(Asset, cls).__register__(module_name)
cls.migrate_updtsource(module_name)
@classmethod
def __setup__(cls):
super(Asset, cls).__setup__()
cls._order.insert(0, ('name', 'ASC'))
cls._order.insert(0, ('date', 'DESC'))
@classmethod
def migrate_updtsource(cls, module_name):
""" replace 'updtsource' by relation
"""
pool = Pool()
Asset2 = pool.get('investment.asset')
asset_table = Asset2.__table_handler__(module_name)
if asset_table.column_exist('updtsource'):
AssetSourceRel = pool.get('investment.asset_source_rel')
tab_asset = Asset2.__table__()
cursor = Transaction().connection.cursor()
query = tab_asset.select(
tab_asset.id,
tab_asset.updtsource,
where = tab_asset.updtsource != None,
)
cursor.execute(*query)
records = cursor.fetchall()
to_create = [{
'asset': x[0],
'source': x[1],
} for x in records]
if len(to_create) > 0:
AssetSourceRel.create(to_create)
asset_table.drop_column('updtsource')
@classmethod
def view_attributes(cls):
return super().view_attributes() + [
('/tree', 'visual',
If(Eval('date', Date()) < Date(delta_days=-5), 'muted',
If(Eval('change_day1', 0) < 0, 'danger',
If(Eval('change_day1', 0) > 0, 'success', '')
))
),
]
@classmethod
def default_currency(cls):
@ -96,32 +194,27 @@ class Asset(ModelSQL, ModelView):
"""
return 4
@fields.depends('updtsource', 'updttime')
def on_change_updtsource(self):
@classmethod
def default_updttime(cls):
""" 14 o'clock UTC
"""
return time(14, 0)
@classmethod
def default_updtdays(cls):
""" default: mon - fri
"""
return 'work'
@fields.depends('updtsources', 'updttime')
def on_change_updtsources(self):
""" clear time-fields
"""
if self.updtsource is None:
if len(self.updtsources) == 0:
self.updttime = None
else :
self.updttime = time(11, 30)
@fields.depends('id', 'currency_digits')
def on_change_with_rate(self, name=None):
""" get current rate
"""
pool = Pool()
Rate = pool.get('investment.rate')
IrDate = pool.get('ir.date')
if self.id:
rates = Rate.search([
('date', '<=', IrDate.today()),
('asset.id', '=', self.id),
], order=[('date', 'DESC')], limit=1)
if len(rates) > 0:
exp = Decimal(Decimal(1) / 10 ** (self.currency_digits or 4))
return rates[0].rate.quantize(exp)
@fields.depends('product', 'uom')
def on_change_product(self):
""" update unit by product
@ -138,77 +231,430 @@ class Asset(ModelSQL, ModelView):
if self.currency:
self.currency_digits = self.currency.digits
@fields.depends('product')
def on_change_with_product_uom(self, name=None):
""" get category of product-uom
@classmethod
def get_name_symbol_sql(cls):
""" get sql for name, uom, digits, etc.
"""
if self.product:
return self.product.default_uom.category.id
pool = Pool()
Product = pool.get('product.product')
ProdTempl = pool.get('product.template')
Uom = pool.get('product.uom')
Currency = pool.get('currency.currency')
tab_asset = cls.__table__()
tab_templ = ProdTempl.__table__()
tab_prod = Product.__table__()
tab_uom = Uom.__table__()
tab_cur = Currency.__table__()
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
""" currency of cashbook
# get translated symbol-column from UOM
(tab1, join1, col1) = Uom.symbol._get_translation_column(Uom, 'symbol')
tab_symb = join1.select(tab1.id, col1.as_('symbol'))
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id,
).join(tab_templ,
condition=tab_templ.id==tab_prod.template,
).join(tab_uom,
condition=tab_templ.default_uom==tab_uom.id,
).join(tab_cur,
condition=tab_asset.currency==tab_cur.id,
).join(tab_symb,
condition=tab_asset.uom==tab_symb.id,
).select(
tab_asset.id,
tab_templ.name,
tab_uom.category.as_('product_uom'),
Concat2(tab_cur.symbol, '/', tab_symb.symbol).as_('symbol'),
)
return (query, tab_asset)
@classmethod
def get_name_symbol(cls, assets, names):
""" get date and rate of asset
"""
if self.currency:
return self.currency.digits
else:
return 2
cursor = Transaction().connection.cursor()
@fields.depends('company', 'currency')
def on_change_with_company_currency(self, name=None):
""" get company-currency if its different from current
asset-currency
(query, tab_asset) = cls.get_name_symbol_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
values = {
'name': record[1],
'product_uom': record[2],
'symbol': record[3],
'asset_symbol': record[0],
}
for name in names:
result[name][record[0]] = values[name]
return result
@classmethod
def search_uom_symbol(cls, names, clause):
""" search in uom
"""
if self.company:
if self.currency:
if self.company.currency.id != self.currency.id:
return self.company.currency.id
return ['OR',
(('uom.rec_name',) + tuple(clause[1:])),
(('currency.rec_name',) + tuple(clause[1:])),
]
@fields.depends('id')
def on_change_with_updtneeded(self, name=None):
""" get state of update
@classmethod
def get_rate_data_sql(cls):
""" get sql for rate/date
"""
pool = Pool()
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset
).select(
tab_asset.id,
Round(tab_rate.rate, tab_asset.currency_digits).as_('rate'),
tab_rate.date,
tab_rate.id.as_('id_rate'),
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc],
)
return (query, tab_asset)
@classmethod
def get_rate_data(cls, assets, names):
""" get date and rate of asset
"""
Asset2 = Pool().get('investment.asset')
if self.id:
if Asset2.search_count([
('updtneeded', '=', True),
('id', '=', self.id)
]) == 1:
return True
return False
cursor = Transaction().connection.cursor()
(query, tab_asset) = cls.get_rate_data_sql()
query.where=tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, rate1, date1, id_rate) = record
asset = Asset2(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
values = {
'rate': record[1].quantize(exp),
'date': record[2],
'change_symbol': id_rate,
}
for name in names:
result[name][record[0]] = values[name]
return result
@classmethod
def search_updtneeded(cls, names, clause):
""" search for assets to update
def search_date(cls, names, clause):
""" search in date
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.date, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def search_rate(cls, names, clause):
""" search in rate
"""
(tab_query, tab_asset) = cls.get_rate_data_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.rate, clause[2]),
)
return [('id', 'in', query)]
@staticmethod
def order_date(tables):
""" order date
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.date,
where=tab_query.id==table.id,
)
return [query]
@staticmethod
def order_rate(tables):
""" order rate
"""
(tab_query, tab_asset) = Asset.get_rate_data_sql()
table, _ = tables[None]
query = tab_query.select(
tab_query.rate,
where=tab_query.id==table.id,
)
return [query]
@classmethod
def get_percentage_sql(cls, days=0, asset_ids=None):
""" get table for percentages and dates,
days: delta-days to past to select percent-value
0=yesterday, 30=last month, ...
"""
pool = Pool()
Asset2 = pool.get('investment.asset')
Rate = pool.get('investment.rate')
IrDate = pool.get('ir.date')
Asset2 = pool.get('investment.asset')
tab_asset = Asset2.__table__()
tab_rate = Rate.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
tab_rate1 = Rate.__table__()
tab_rate2 = Rate.__table__()
context = Transaction().context
query_date = context.get('qdate', IrDate.today())
query_time = context.get('qtime', CurrentTime())
query_date = context.get('qdate', CurrentDate())
where_asset = tab_rate1.date <= query_date
if isinstance(asset_ids, list):
where_asset &= tab_asset.id.in_(asset_ids)
query = tab_asset.join(tab_rate,
condition=(tab_asset.id==tab_rate.asset) & \
(tab_rate.date == query_date),
type_ = 'LEFT OUTER',
).select(tab_asset.id,
where=Operator(
Case(
((tab_rate.id == None) & \
(tab_asset.updtsource != None) & \
(tab_asset.updttime <= query_time), True),
default_ = False,
),
clause[2]),
tab_today = tab_asset.join(tab_rate1,
condition=tab_asset.id==tab_rate1.asset,
).select(
tab_asset.id,
tab_rate1.date,
tab_rate1.rate,
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate1.date.desc],
where=where_asset,
)
return [('id', '=', query)]
days_diff = days + 5
query = tab_today.join(tab_rate2,
condition=(tab_today.id==tab_rate2.asset) & \
(tab_today.date > (tab_rate2.date + days)) & \
(tab_today.date <= (tab_rate2.date + days_diff)),
type_ = 'LEFT OUTER',
).select(
tab_today.id,
tab_today.date,
tab_today.rate,
(tab_today.rate * 100.0 / NullIf(tab_rate2.rate, 0.00) - 100.0).as_('percent'),
distinct_on=[tab_today.id],
order_by=[tab_today.id, tab_rate2.date.desc]
)
return query
@staticmethod
def order_change_day1(tables):
""" order day1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=0)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month1(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=30)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month3(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=90)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month6(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=180)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
return [query]
@staticmethod
def order_change_month12(tables):
""" order month1
"""
Assert = Pool().get('investment.asset')
tab_asset = Asset.get_percentage_sql(days=365)
table, _ = tables[None]
query = tab_asset.select(
tab_asset.percent,
where=tab_asset.id==table.id,
)
return [query]
@classmethod
def search_percentage(cls, names, clause):
""" search for percentages
"""
Operator = fields.SQL_OPERATORS[clause[1]]
field_name = clause[0][len('change_'):]
tab_percent = cls.get_percentage_sql(days={
'day1': 0,
'month1': 30,
'month3': 90,
'month6': 180,
'month12': 365,
}[field_name])
query = tab_percent.select(tab_percent.id,
where=Operator(Round(tab_percent.percent, 2),
clause[2]))
return [('id', 'in', query)]
@classmethod
def get_percentage_change(cls, assets, names):
""" get percentage per period
"""
cursor = Transaction().connection.cursor()
result = {x:{y.id: None for y in assets} for x in names}
exp = Decimal(Decimal(1) / 10 ** digits_percent)
asset_id_lst = [x.id for x in assets]
for x in names:
tab_percent = cls.get_percentage_sql(
days={
'change_day1': 0,
'change_month1': 30,
'change_month3': 90,
'change_month6': 180,
'change_month12': 365,
}[x],
asset_ids=asset_id_lst,
)
cursor.execute(*tab_percent)
records = cursor.fetchall()
for record in records:
result[x][record[0]] = record[3].quantize(exp) \
if record[3] is not None else None
return result
@classmethod
def get_next_update_datetime_sql(cls):
""" get sql for datetime of next planned update
"""
pool = Pool()
Asset = pool.get('investment.asset')
AssetSourceRel = pool.get('investment.asset_source_rel')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
tab_rel = AssetSourceRel.__table__()
context = Transaction().context
query_date = context.get('qdate', CurrentDate() - Literal(1))
# get last date of rate
tab_date = tab_asset.join(tab_rel,
# link to asset-source-relation to check if
# there are online-sources set
condition=tab_rel.asset == tab_asset.id,
).join(tab_rate,
condition=tab_asset.id == tab_rate.asset,
type_ = 'LEFT OUTER',
).select(
tab_asset.id,
(Coalesce(tab_rate.date, query_date) + Literal(1)).as_('date'),
tab_asset.updtdays,
tab_asset.updttime,
distinct_on = [tab_asset.id],
order_by = [tab_asset.id, tab_rate.date.desc],
)
query = tab_date.select(
tab_date.id,
(Case(
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 0), tab_date.date + Literal(1)),
((tab_date.updtdays == 'work') & \
(Extract('dow', tab_date.date) == 6), tab_date.date + Literal(2)),
else_ = tab_date.date,
) + tab_date.updttime).as_('updttime'),
)
return query
@classmethod
def get_nextupdates(cls, assets, names):
""" get timestamp of next update
"""
Asset2 = Pool().get('investment.asset')
tab_updt = Asset2.get_next_update_datetime_sql()
cursor = Transaction().connection.cursor()
query = tab_updt.select(
tab_updt.id,
tab_updt.updttime,
where=tab_updt.id.in_([x.id for x in assets]),
)
cursor.execute(*query)
records = cursor.fetchall()
result = {x:{y.id: None for y in assets} for x in names}
for record in records:
(id1, updt) = record
r1 = {'nextupdate': updt}
for n in names:
result[n][id1] = r1[n]
return result
@classmethod
def search_nextupdate(cls, names, clause):
""" search for assets to update
"""
Asset2 = Pool().get('investment.asset')
tab_updt = Asset2.get_next_update_datetime_sql()
Operator = fields.SQL_OPERATORS[clause[1]]
query = tab_updt.select(
tab_updt.id,
where=Operator(tab_updt.updttime, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def get_identifier_sql(cls, tab_asset):
@ -244,6 +690,70 @@ class Asset(ModelSQL, ModelView):
)
return query
@staticmethod
def order_name(tables):
""" order name
"""
pool = Pool()
Templ = pool.get('product.template')
Product = pool.get('product.product')
Asset = pool.get('investment.asset')
table, _ = tables[None]
tab_asset = Asset.__table__()
tab_prod = Product.__table__()
tab_templ = Templ.__table__()
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id
).join(tab_templ,
condition=tab_templ.id==tab_prod.template
).select(tab_templ.name,
where=tab_asset.id==table.id
)
return [query]
@staticmethod
def order_wkn(tables):
""" order wkn
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'wkn'),
where=tab_ids.id==table.id,
)
return [query]
@staticmethod
def order_isin(tables):
""" order isin
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'isin'),
where=tab_ids.id==table.id,
)
return [query]
@staticmethod
def order_secsymb(tables):
""" order secsymb
"""
Asset = Pool().get('investment.asset')
tab_ids = Asset.get_identifier_sql(Asset.__table__())
table, _ = tables[None]
query = tab_ids.select(
getattr(tab_ids, 'secsymb'),
where=tab_ids.id==table.id,
)
return [query]
@classmethod
def search_identifier(cls, names, clause):
""" search in identifier
@ -294,17 +804,29 @@ class Asset(ModelSQL, ModelView):
def get_rec_name(self, name):
""" record name
"""
return '%(prod)s [%(curr)s/%(unit)s]' % {
return '%(prod)s | %(rate)s %(unit)s | %(date)s' % {
'prod': getattr(self.product, 'rec_name', '-'),
'curr': getattr(self.currency, 'rec_name', '-'),
'unit': getattr(self.uom, 'rec_name', '-'),
'unit': self.symbol,
'rate': Report.format_number(self.rate, lang=None,
digits=self.currency_digits or 4) \
if self.rate is not None else '-',
'date': Report.format_date(self.date) if self.date is not None else '-',
}
@classmethod
def search_rec_name(cls, name, clause):
""" search in rec_name
"""
return [('product.rec_name',) + tuple(clause[1:])]
return ['OR',
('product.rec_name',) + tuple(clause[1:]),
('product.identifiers.code',) + tuple(clause[1:]),
]
@classmethod
def after_update_actions(cls, assets):
""" run activities after rate-update
"""
pass
@classmethod
def cron_update(cls):
@ -313,33 +835,31 @@ class Asset(ModelSQL, ModelView):
pool = Pool()
Asset2 = pool.get('investment.asset')
OnlineSource = pool.get('investment.source')
context = Transaction().context
query_time = context.get('qdatetime', CurrentTimestamp())
to_run_activities = []
for asset in Asset2.search([
('updtneeded', '=', True),
('nextupdate', '<=', query_time),
]):
OnlineSource.update_rate(asset)
if OnlineSource.update_rate(asset):
to_run_activities.append(asset)
@classmethod
def create(cls, vlist):
""" add debit/credit
"""
vlist = [x.copy() for x in vlist]
for values in vlist:
if 'updtsource' in values.keys():
if values['updtsource'] is None:
values['updttime'] = None
return super(Asset, cls).create(vlist)
@classmethod
def write(cls, *args):
""" deny update if cashbook.line!='open',
add or update debit/credit
"""
actions = iter(args)
for lines, values in zip(actions, actions):
if 'updtsource' in values.keys():
if values['updtsource'] is None:
values['updttime'] = None
super(Asset, cls).write(*args)
if len(to_run_activities) > 0:
cls.after_update_actions(to_run_activities)
# end Asset
class AssetSourceRel(ModelSQL):
'Asset Source Relation'
__name__ = 'investment.asset_source_rel'
source = fields.Many2One(string='Online Source', select=True,
required=True, model_name='investment.source',
ondelete='CASCADE')
asset = fields.Many2One(string='Asset', select=True,
required=True, model_name='investment.asset',
ondelete='CASCADE')
# end AssetSourceRel

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

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>

147
diagram.py Normal file
View file

@ -0,0 +1,147 @@
# -*- 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.id', '=', self.asset.id)]
elif self.scaling == 'view':
query = [
('asset.id', '=', self.asset.id),
('date', '>=', self.chart.used_start_date()),
('date', '<=', self.chart.used_end_date()),
]
elif self.scaling == 'six':
query = [
('asset.id', '=', self.asset.id),
('date', '>=', self.chart.used_start_date() - timedelta(days=180)),
('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.id', '=', asset_id),
], limit=1, order=[('date', 'DESC')])
after = Rate.search([
('date', '>', query_date),
('asset.id', '=', asset_id),
], limit=1, order=[('date', 'ASC')])
if (len(before) == 1) and (len(after) == 1):
result = cls.interpolate_linear(
(after[0].date, after[0].rate),
(before[0].date, before[0].rate),
query_date
)
return result
elif len(before) == 1:
return before[0].rate
elif len(after) == 1:
return after[0].rate
return super(ChartPoint, cls).get_interpolated_val(keyname, query_date)
@classmethod
def get_table_parts(cls):
""" return a list of tables to union,
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

207
import_wiz.py Normal file
View file

@ -0,0 +1,207 @@
# -*- 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 :
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 :
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,18 @@ 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"
##############
# ir.ui.menu #
@ -66,6 +78,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 +162,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 +182,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 +206,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 +242,109 @@ 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"
###############################
# 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 +390,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 +522,14 @@ 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."
###################
# investment.rate #
@ -393,3 +569,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,18 @@ 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.ui.menu,name:menu_investment"
msgid "Investment"
msgstr "Investment"
@ -46,6 +58,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 +122,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 +142,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 +166,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 +202,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 +342,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 +474,14 @@ 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 "model:investment.rate,name:"
msgid "Rate"
msgstr "Rate"
@ -350,3 +510,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,15 @@ 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>
</data>
</tryton>

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.
@ -7,10 +7,10 @@ from string import Template
import requests, logging, html2text, 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
logger = logging.getLogger(__name__)
@ -28,42 +28,65 @@ 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', \
'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)
query_method = fields.Selection(string='Method', required=True,
help='Select the method to retrieve the data.',
selection='get_query_methods')
url = fields.Char(string='URL', states=STATES_WEB, depends=DEPENDS_WEB)
nohtml = fields.Boolean(string='Remove HTML',
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='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)
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.')
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.'),
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')
@ -85,6 +108,17 @@ class OnlineSource(ModelSQL, ModelView):
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
@ -180,30 +214,56 @@ class OnlineSource(ModelSQL, ModelView):
symbol = self.symbol,
)
@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, 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,
)
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.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):
""" generate url
@ -221,48 +281,56 @@ 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,
)
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):

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>

66
rate.py
View file

@ -1,15 +1,15 @@
# -*- 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
class Rate(ModelSQL, ModelView):
class Rate(SymbolMixin, ModelSQL, ModelView):
'Rate'
__name__ = 'investment.rate'
@ -17,18 +17,19 @@ class Rate(ModelSQL, ModelView):
select=True, ondelete='CASCADE',
model_name='investment.asset')
date = fields.Date(string='Date', required=True, select=True)
rate = fields.Numeric(string='Rate', required=True,
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')
readonly=True), 'get_rate_data')
currency = fields.Function(fields.Many2One(string='Currency',
readonly=True, model_name='currency.currency'),
'on_change_with_currency')
'get_rate_data')
uom = fields.Function(fields.Many2One(string='Uom',
readonly=True, model_name='product.uom'),
'on_change_with_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 +52,39 @@ 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

@ -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:
@ -97,7 +97,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
'view/*.xml', 'icon/*.svg',
'view/*.xml', 'icon/*.svg', 'scripts/*.py',
'versiondep.txt', 'README.rst']),
},
@ -106,5 +106,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,72 @@
<?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_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>
</data>
</tryton>

View file

@ -7,12 +7,14 @@ import unittest
from trytond.modules.investment.tests.test_asset import AssetTestCase
from trytond.modules.investment.tests.test_rate import RateTestCase
from trytond.modules.investment.tests.test_source import SourceTestCase
from trytond.modules.investment.tests.test_wizard import WizardTestCase
__all__ = ['suite']
class InvestmentTestCase(\
WizardTestCase, \
SourceTestCase, \
RateTestCase,\
AssetTestCase,\

View file

@ -7,8 +7,9 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.modules.company.tests import create_company
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from decimal import Decimal
from datetime import time, date
from datetime import time, date, datetime
class AssetTestCase(ModuleTestCase):
@ -61,9 +62,9 @@ class AssetTestCase(ModuleTestCase):
'currency_digits': 4,
'uom': product.default_uom.id,
}])
self.assertEqual(asset.rec_name, '%s [usd/%s]' % (
self.assertEqual(asset.rec_name, '%s | - usd/%s | -' % (
product.rec_name,
asset.uom.rec_name,
asset.uom.symbol,
))
self.assertEqual(asset.currency.rec_name, 'usd')
self.assertEqual(asset.currency_digits, 4)
@ -75,6 +76,8 @@ class AssetTestCase(ModuleTestCase):
def test_asset_create(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
@ -83,6 +86,378 @@ class AssetTestCase(ModuleTestCase):
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset.symbol, 'usd/u')
self.assertEqual(asset.asset_symbol.symbol, 'usd/u')
# check ranges
Asset.write(*[
[asset],
{
'currency_digits': 1,
}])
self.assertRaisesRegex(UserError,
'ss',
Asset.write,
*[[asset], {
'currency_digits': -1,
}])
@with_transaction()
def test_asset_rec_name(self):
""" create asset
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
}])
self.assertEqual(asset.rec_name, 'Product 1 | 2.4500 usd/u | 05/15/2022')
self.assertEqual(Asset.search_count([('name', '=', 'Product 1')]), 1)
@with_transaction()
def test_asset_order_and_search_rate_and_date(self):
""" create asset, check order of rate + date
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product1 = self.prep_asset_product(
name='Product 1',
description='some asset')
product2 = self.prep_asset_product(
name='Product 2',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product1)
asset2 = self.prep_asset_item(
company=company,
product = product2)
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 18),
'rate': Decimal('3.5'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 17),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 14),
'rate': Decimal('2.4'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 | 3.5000 usd/u | 05/18/2022')
self.assertEqual(asset2.rec_name, 'Product 2 | 2.6000 usd/u | 05/17/2022')
assets = Asset.search([], order=[('date', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 17))
self.assertEqual(assets[1].date, date(2022, 5, 18))
assets = Asset.search([], order=[('date', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].date, date(2022, 5, 18))
self.assertEqual(assets[1].date, date(2022, 5, 17))
assets = Asset.search([], order=[('rate', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('2.6'))
self.assertEqual(assets[1].rate, Decimal('3.5'))
assets = Asset.search([], order=[('rate', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].rate, Decimal('3.5'))
self.assertEqual(assets[1].rate, Decimal('2.6'))
self.assertEqual(Asset.search_count([
('date', '=', date(2022, 5, 17)),
]), 1)
self.assertEqual(Asset.search_count([
('date', '>=', date(2022, 5, 17)),
]), 2)
self.assertEqual(Asset.search_count([
('date', '<', date(2022, 5, 17)),
]), 0)
@with_transaction()
def test_asset_percentages_dateselect1(self):
""" create asset, add rates, check selection of
specific date - fixed date
"""
Asset = Pool().get('investment.asset')
cursor = Transaction().connection.cursor()
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 5, 12),
'rate': Decimal('2.0'),
}, {
'date': date(2022, 5, 3),
'rate': Decimal('3.6'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 5, 12))
self.assertEqual(asset1.rates[3].date, date(2022, 5, 3))
# query fixed date
tab_percent = Asset.get_percentage_sql(days=0)
with Transaction().set_context({
'qdate': date(2022, 5, 16),
}):
query = tab_percent.select(
tab_percent.id,
tab_percent.date,
tab_percent.percent,
where=tab_percent.id==asset1.id,
)
cursor.execute(*query)
records = cursor.fetchall()
# there should be one record, three colums
self.assertEqual(len(records), 1)
self.assertEqual(len(records[0]), 3)
self.assertEqual(records[0][0], asset1.id)
self.assertEqual(records[0][1], date(2022, 5, 16))
self.assertEqual(records[0][2].quantize(Decimal('0.01')), Decimal('6.12'))
@with_transaction()
def test_asset_percentages_daterange(self):
""" create asset, add rates, check selection of
value
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
asset2 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
self.assertEqual(asset2.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}])],
},
[asset2],
{
'rates': [('create', [{
'date': date(2022, 5, 14),
'rate': Decimal('5.75'),
}, {
'date': date(2022, 5, 15),
'rate': Decimal('5.25'),
}])],
},
])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(asset2.rec_name, 'Product 1 | 5.2500 usd/u | 05/15/2022')
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset2.change_day1, Decimal('-8.7'))
self.assertEqual(asset1.change_month1, None)
self.assertEqual(asset2.change_month1, None)
self.assertEqual(asset1.change_month3, None)
self.assertEqual(asset2.change_month3, None)
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset2.change_month6, None)
self.assertEqual(asset1.change_month12, None)
self.assertEqual(asset2.change_month12, None)
# check ordering
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'ASC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('-8.7'))
self.assertEqual(assets[1].change_day1, Decimal('6.12'))
assets = Asset.search([
('change_day1', '!=', Decimal('0.0')),
], order=[('change_day1', 'DESC')])
self.assertEqual(len(assets), 2)
self.assertEqual(assets[0].change_day1, Decimal('6.12'))
self.assertEqual(assets[1].change_day1, Decimal('-8.7'))
# check 5-day-range
# four days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 12),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 12))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# five days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 11),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 11))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
# six days
Asset.write(*[
[asset1],
{
'rates': [('write', [asset1.rates[1]], {
'date': date(2022, 5, 10),
})],
}])
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 10))
self.assertEqual(asset1.change_day1, None)
@with_transaction()
def test_asset_percentges_values(self):
""" create asset, add rates, check percentages
"""
Asset = Pool().get('investment.asset')
company = self.prep_asset_company()
product = self.prep_asset_product(
name='Product 1',
description='some asset')
asset1 = self.prep_asset_item(
company=company,
product = product)
self.assertEqual(asset1.rec_name, 'Product 1 | - usd/u | -')
Asset.write(*[
[asset1],
{
'rates': [('create', [{
'date': date(2022, 5, 15),
'rate': Decimal('2.45'),
}, {
'date': date(2022, 5, 16),
'rate': Decimal('2.6'),
}, {
'date': date(2022, 4, 14),
'rate': Decimal('2.2'),
}, {
'date': date(2022, 2, 14),
'rate': Decimal('2.8'),
},])],
}])
self.assertEqual(asset1.rec_name, 'Product 1 | 2.6000 usd/u | 05/16/2022')
self.assertEqual(len(asset1.rates), 4)
self.assertEqual(asset1.rates[0].date, date(2022, 5, 16))
self.assertEqual(asset1.rates[1].date, date(2022, 5, 15))
self.assertEqual(asset1.rates[2].date, date(2022, 4, 14))
self.assertEqual(asset1.rates[3].date, date(2022, 2, 14))
self.assertEqual(asset1.change_day1, Decimal('6.12'))
self.assertEqual(asset1.change_month1, Decimal('18.18'))
self.assertEqual(asset1.change_month3, Decimal('-7.14'))
self.assertEqual(asset1.change_month6, None)
self.assertEqual(asset1.change_month12, None)
# call order-functions
Asset.search([], order=[('change_day1', 'ASC')])
Asset.search([], order=[('change_month1', 'ASC')])
Asset.search([], order=[('change_month3', 'ASC')])
Asset.search([], order=[('change_month6', 'ASC')])
Asset.search([], order=[('change_month12', 'ASC')])
# searcher
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.1'))]),
1)
self.assertEqual(
Asset.search_count([('change_day1', '>', Decimal('6.15'))]),
0)
self.assertEqual(
Asset.search_count([('change_day1', '=', Decimal('6.12'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.0'))]),
1)
self.assertEqual(
Asset.search_count([('change_month1', '>', Decimal('18.18'))]),
0)
self.assertEqual(
Asset.search_count([('change_month1', '=', Decimal('18.18'))]),
1)
self.assertEqual(
Asset.search_count([('change_month3', '=', Decimal('-7.14'))]),
1)
self.assertEqual(
Asset.search_count([('change_month6', '=', None)]),
1)
@with_transaction()
def test_asset_check_onlinesource_onoff(self):
@ -105,18 +480,19 @@ class AssetTestCase(ModuleTestCase):
'name': 'Source 1',
}])
self.assertEqual(asset.updtsource, None)
self.assertEqual(asset.updttime, None)
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, time(14,0))
asset.updtsource = o_source
asset.updtsources = [o_source]
asset.updttime = time(10, 45)
asset.save()
self.assertEqual(asset.updtsource.rec_name, 'Source 1')
self.assertEqual(len(asset.updtsources), 1)
self.assertEqual(asset.updtsources[0].rec_name, 'Source 1')
self.assertEqual(asset.updttime, time(10, 45))
asset.updtsource = None
asset.on_change_updtsource()
self.assertEqual(asset.updtsource, None)
asset.updtsources = []
asset.on_change_updtsources()
self.assertEqual(len(asset.updtsources), 0)
self.assertEqual(asset.updttime, None)
@with_transaction()
@ -143,84 +519,81 @@ class AssetTestCase(ModuleTestCase):
Asset.write(*[
[asset],
{
'updtsource': o_source.id,
'updtsources': [('add', [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),
'qdate': date(2022, 10, 14), # friday
}):
# no rates exists - wait for 10:45
self.assertEqual(asset.updtneeded, True)
# 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([('updtneeded', '=', True)]),
Asset.search_count([('nextupdate', '<', datetime(2022, 10, 17, 10, 45))]),
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)]),
Asset.search_count([('nextupdate', '>=', datetime(2022, 10, 17, 10, 45))]),
1)
# add rate at yesterday
# add rate at next monday
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 10, 14),
'date': date(2022, 10, 17), # monday
'rate': Decimal('1.5'),
}])],
}])
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)
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))
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)
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, 15),
'date': date(2022, 10, 18),
'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)
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):
@ -280,6 +653,16 @@ class AssetTestCase(ModuleTestCase):
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)
@ -291,6 +674,36 @@ class AssetTestCase(ModuleTestCase):
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

View file

@ -46,5 +46,7 @@ class RateTestCase(ModuleTestCase):
self.assertEqual(asset.rates[0].uom.rec_name, 'Unit')
self.assertEqual(asset.rates[0].asset_digits, 4)
self.assertEqual(asset.rates[0].currency.rec_name, 'usd')
self.assertEqual(asset.rates[0].symbol, '%')
self.assertEqual(asset.change_symbol.symbol, '%')
# end RateTestCase

View file

@ -8,7 +8,7 @@ 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 datetime import time, date, datetime
from unittest.mock import MagicMock
from requests import Response
import requests
@ -64,28 +64,39 @@ class SourceTestCase(ModuleTestCase):
Asset.write(*[
[asset],
{
'updtsource': osource.id,
'updtsources': [('add', [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
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)
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'))
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):

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

View file

@ -1,11 +1,13 @@
[tryton]
version=6.0.0
version=6.0.23
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,15 @@ 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,12 @@ 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"/>
<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" symbol="change_symbol"/>
<field name="change_month1" symbol="change_symbol"/>
<field name="change_month3" symbol="change_symbol"/>
<field name="change_month6" symbol="change_symbol"/>
<field name="date"/>
<field name="rate" symbol="asset_symbol"/>
<field name="isin"/>
<field name="secsymb"/>
<field name="wkn"/>
<field name="rate"/>
<field name="currency"/>
<field name="uom"/>
<field name="wkn" />
</tree>

14
view/graph_form.xml Normal file
View file

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

View file

@ -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,21 @@ 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"
<label xalign="0.0" colspan="3" name="nohtml"
string="URL parameter placeholders: ${isin}, ${nsin}, ${symbol}"/>
<newline/>
<separator colspan="6" id="seprgx" string="Regular expressions to find data"/>
<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>