investment/asset.py

505 lines
18 KiB
Python
Raw Normal View History

2022-11-09 20:49:33 +00:00
# -*- 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.model import ModelView, ModelSQL, fields
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.pyson import Eval, Bool, And
from trytond.report import Report
from decimal import Decimal
from datetime import time
2022-11-24 22:17:44 +00:00
from sql.functions import CurrentTime, CurrentDate
2022-11-17 21:51:29 +00:00
from sql.conditionals import Case
2022-11-24 22:17:44 +00:00
from sql import Literal
2022-11-09 20:49:33 +00:00
class Asset(ModelSQL, ModelView):
'Asset'
__name__ = 'investment.asset'
name = fields.Function(fields.Char(string='Name', readonly=True),
'on_change_with_name')
2022-11-09 20:49:33 +00:00
company = fields.Many2One(string='Company', model_name='company.company',
required=True, ondelete="RESTRICT")
2022-11-10 21:31:22 +00:00
product = fields.Many2One(string='Product', required=True,
model_name='product.product', ondelete='RESTRICT',
domain=[('type', '=', 'assets')])
product_uom = fields.Function(fields.Many2One(string='UOM Category',
readonly=True, model_name='product.uom.category',
help='Category of unit on the product.'),
'on_change_with_product_uom')
uom = fields.Many2One(string='UOM', required=True,
model_name='product.uom', ondelete='RESTRICT',
states={
'readonly': ~Bool(Eval('product')),
},
domain=[
('category', '=', Eval('product_uom')),
], depends=['product_uom', 'product'])
2022-11-13 21:57:40 +00:00
rates = fields.One2Many(string='Rates', field='asset',
model_name='investment.rate')
rate = fields.Function(fields.Numeric(string='Current Rate',
readonly=True, digits=(16, Eval('currency_digits', 4)),
depends=['currency_digits']), 'get_rate_data')
date = fields.Function(fields.Date(string='Date', readonly=True,
help='Date of current rate'), 'get_rate_data')
2022-11-09 20:49:33 +00:00
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')
currency = fields.Many2One(string='Currency', select=True,
required=True, model_name='currency.currency', ondelete='RESTRICT')
currency_digits = fields.Integer(string='Currency Digits',
required=True)
wkn = fields.Function(fields.Char(string='NSIN', readonly=True,
help='National Securities Identifying Number'),
'get_identifiers', searcher='search_identifier')
isin = fields.Function(fields.Char(string='ISIN', readonly=True,
help='International Securities Identification Number'),
'get_identifiers', searcher='search_identifier')
secsymb = fields.Function(fields.Char(string='Symbol', readonly=True,
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')
2022-11-17 21:51:29 +00:00
updttime = fields.Time(string='Time',
states={
'readonly': ~Bool(Eval('updtsource')),
}, depends=['updtsource'])
2022-11-17 21:51:29 +00:00
updtneeded = fields.Function(fields.Boolean(string='Course update needed',
readonly=True),
'on_change_with_updtneeded', searcher='search_updtneeded')
2022-11-24 22:17:44 +00:00
# percentage change
change_today = fields.Function(fields.Numeric(string='Previous Day',
help='percentage change in value compared to the previous day',
readonly=True, digits=(16,1)),
'get_percentage_change')
change_month = fields.Function(fields.Numeric(string='1 Month',
help='percentage change in value compared to last month',
readonly=True, digits=(16,1)),
'get_percentage_change')
change_3month = fields.Function(fields.Numeric(string='3 Months',
help='percentage change in value during 3 months',
readonly=True, digits=(16,1)),
'get_percentage_change')
change_6month = fields.Function(fields.Numeric(string='6 Months',
help='percentage change in value during 6 months',
readonly=True, digits=(16,1)),
'get_percentage_change')
change_12month = fields.Function(fields.Numeric(string='1 Year',
help='percentage change in value during 1 year',
readonly=True, digits=(16,1)),
'get_percentage_change')
2022-11-09 20:49:33 +00:00
@classmethod
def default_currency(cls):
""" currency of company
"""
Company = Pool().get('company.company')
company = cls.default_company()
if company:
company = Company(company)
if company.currency:
return company.currency.id
@staticmethod
def default_company():
return Transaction().context.get('company') or None
2022-11-10 21:31:22 +00:00
@classmethod
def default_currency_digits(cls):
2022-11-13 21:57:40 +00:00
""" default: 4
2022-11-10 21:31:22 +00:00
"""
2022-11-13 21:57:40 +00:00
return 4
2022-11-10 21:31:22 +00:00
2022-11-24 22:17:44 +00:00
@classmethod
def get_percentage_sql(cls, table_asset):
""" get table for percentages and dates
"""
pool = Pool()
Rate = pool.get('investment.rate')
tab_rate_today = Rate.__table__()
tab_rate_1day = Rate.__table__()
tab_rate_1month = Rate.__table__()
tab_rate_3month = Rate.__table__()
context = Transaction().context
query_date = context.get('qdate', CurrentDate())
query_today = table_asset.join(tab_rate_today,
condition=table_asset.id==tab_rate_today.asset,
).select(
table_asset.id,
tab_rate_today.date,
tab_rate_today.rate,
distinct_on=[table_asset.id],
order_by=[table_asset.id, tab_rate_today.date.desc],
where=tab_rate_today.date <= query_date,
)
query = query_today.join(tab_rate_1day,
# select newest date from yesterday until 3 days old
condition=(query_today.id==tab_rate_1day.asset) & \
(query_today.date > tab_rate_1day.date) & \
(query_today.date < tab_rate_1day.date + Literal(3)),
type_ = 'LEFT OUTER',
).join(tab_rate_1month,
# select newest date from 1 month ago until +3 days old
condition=(query_today.id==tab_rate_1month.asset) & \
(query_today.date > tab_rate_1month.date + Literal(30)) & \
(query_today.date < tab_rate_1month.date + Literal(33)),
type_ = 'LEFT OUTER',
).select(
query_today.id,
query_today.date,
Case(
((tab_rate_1day.rate != None) & (query_today.rate != None) & \
(tab_rate_1day.rate != Literal(0.0)),
query_today.rate * Literal(100.0) / tab_rate_1day.rate - Literal(100.0)),
else_ = None,
).as_('day1'),
Case(
((tab_rate_1month.rate != None) & (query_today.rate != None) & \
(tab_rate_1month.rate != Literal(0.0)),
query_today.rate * Literal(100.0) / tab_rate_1month.rate - Literal(100.0)),
else_ = None,
).as_('month1'),
distinct_on=[query_today.id],
order_by=[
query_today.id,
tab_rate_1day.date.desc,
tab_rate_1month.date.desc,
],
)
return query
@classmethod
def get_percentage_change(cls, assets, names):
""" get percentage per period
"""
pool = Pool()
Asset = pool.get('investment.asset')
tab_asset = Asset.__table__()
cursor = Transaction().connection.cursor()
tab_percent = cls.get_percentage_sql(tab_asset)
query = tab_percent.select(
tab_percent.id,
tab_percent.day1,
tab_percent.month1,
where=tab_percent.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 = {
'change_today': record[1].quantize(Decimal('0.1')) \
if record[1] is not None else None,
'change_month': record[2].quantize(Decimal('0.1')) \
if record[2] is not None else None,
'change_3month': None,
'change_6month': None,
'change_12month': None,
}
for name in names:
result[name][record[0]] = values[name]
print('-- result:', result)
return result
@classmethod
def get_rate_data(cls, assets, names):
""" get date and rate of asset
"""
pool = Pool()
Asset = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset.__table__()
tab_rate = Rate.__table__()
cursor = Transaction().connection.cursor()
query = tab_asset.join(tab_rate,
condition=tab_asset.id==tab_rate.asset
).select(
tab_asset.id,
tab_rate.rate,
tab_rate.date,
distinct_on=[tab_asset.id],
order_by=[tab_asset.id, tab_rate.date.desc],
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) = record
asset = Asset(id1)
exp = Decimal(Decimal(1) / 10 ** (asset.currency_digits or 4))
values = {'rate': record[1].quantize(exp), 'date': record[2]}
for name in names:
result[name][record[0]] = values[name]
return result
2022-11-17 21:51:29 +00:00
@fields.depends('updtsource', 'updttime')
def on_change_updtsource(self):
""" clear time-fields
"""
if self.updtsource is None:
2022-11-17 21:51:29 +00:00
self.updttime = None
else :
2022-11-17 21:51:29 +00:00
self.updttime = time(11, 30)
2022-11-10 21:31:22 +00:00
@fields.depends('product', 'uom')
def on_change_product(self):
""" update unit by product
"""
if self.product:
self.uom = self.product.default_uom
return
self.uom = None
@fields.depends('currency', 'currency_digits')
def on_change_currency(self):
""" update currency_digits by value on currency
"""
if self.currency:
self.currency_digits = self.currency.digits
@fields.depends('product')
def on_change_with_name(self, name=None):
""" get name of product
"""
if self.product:
return self.product.name
2022-11-17 21:51:29 +00:00
@fields.depends('product')
def on_change_with_product_uom(self, name=None):
""" get category of product-uom
"""
if self.product:
return self.product.default_uom.category.id
@fields.depends('currency')
def on_change_with_currency_digits(self, name=None):
""" currency of cashbook
"""
if self.currency:
return self.currency.digits
else:
return 2
@fields.depends('company', 'currency')
def on_change_with_company_currency(self, name=None):
""" get company-currency if its different from current
asset-currency
"""
if self.company:
if self.currency:
if self.company.currency.id != self.currency.id:
return self.company.currency.id
@fields.depends('id')
def on_change_with_updtneeded(self, name=None):
""" get state of update
"""
Asset2 = Pool().get('investment.asset')
if self.id:
if Asset2.search_count([
('updtneeded', '=', True),
('id', '=', self.id)
]) == 1:
return True
return False
@classmethod
def search_updtneeded(cls, names, clause):
""" search for assets to update
"""
pool = Pool()
Asset2 = pool.get('investment.asset')
Rate = pool.get('investment.rate')
tab_asset = Asset2.__table__()
tab_rate = Rate.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
context = Transaction().context
2022-11-24 22:17:44 +00:00
query_date = context.get('qdate', CurrentDate())
2022-11-17 21:51:29 +00:00
query_time = context.get('qtime', CurrentTime())
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]),
)
return [('id', 'in', query)]
2022-11-17 21:51:29 +00:00
@classmethod
def get_identifier_sql(cls, tab_asset):
""" sql-query for identifiers
"""
pool = Pool()
Product = pool.get('product.product')
Identifier = pool.get('product.identifier')
tab_prod = Product.__table__()
tab_wkn = Identifier.__table__()
tab_secsymb = Identifier.__table__()
tab_isin = Identifier.__table__()
query = tab_asset.join(tab_prod,
condition=tab_asset.product==tab_prod.id,
).join(tab_wkn,
condition=(tab_prod.id==tab_wkn.product) & \
(tab_wkn.type == 'wkn'),
type_ = 'LEFT OUTER',
).join(tab_secsymb,
condition=(tab_prod.id==tab_secsymb.product) & \
(tab_secsymb.type == 'secsymb'),
type_ = 'LEFT OUTER',
).join(tab_isin,
condition=(tab_prod.id==tab_isin.product) & \
(tab_isin.type == 'isin'),
type_ = 'LEFT OUTER',
).select(
tab_asset.id,
tab_wkn.code.as_('wkn'),
tab_secsymb.code.as_('secsymb'),
tab_isin.code.as_('isin'),
)
return query
@classmethod
def search_identifier(cls, names, clause):
""" search in identifier
"""
pool = Pool()
Asset = pool.get('investment.asset')
tab_asset = Asset.__table__()
Operator = fields.SQL_OPERATORS[clause[1]]
tab_ids = cls.get_identifier_sql(tab_asset)
field_qu = getattr(tab_ids, names)
query = tab_ids.join(tab_asset,
condition=tab_ids.id==tab_asset.id,
).select(
tab_asset.id,
where=Operator(field_qu, clause[2]) & \
(field_qu != None),
)
return [('id', 'in', query)]
@classmethod
def get_identifiers(cls, assets, names):
""" get identifiers of assets
"""
pool = Pool()
Asset = pool.get('investment.asset')
tab_asset = Asset.__table__()
cursor = Transaction().connection.cursor()
result = {x:{y.id: None for y in assets} for x in names}
query = cls.get_identifier_sql(tab_asset)
query.where = tab_asset.id.in_([x.id for x in assets])
cursor.execute(*query)
l1 = cursor.fetchall()
for x in l1:
(id1, wkn, secsymb, isin) = x
r1 = {'wkn': wkn, 'secsymb': secsymb, 'isin': isin}
for n in names:
result[n][id1] = r1[n]
return result
2022-11-10 21:31:22 +00:00
def get_rec_name(self, name):
2022-11-22 21:43:28 +00:00
""" record name
"""
return '%(prod)s - %(rate)s %(curr)s/%(unit)s [%(date)s]' % {
2022-11-22 21:43:28 +00:00
'prod': getattr(self.product, 'rec_name', '-'),
2022-11-24 22:17:44 +00:00
'curr': getattr(self.currency, 'symbol', '-'),
2022-11-22 21:43:28 +00:00
'unit': getattr(self.uom, 'rec_name', '-'),
'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 '-',
2022-11-22 21:43:28 +00:00
}
2022-11-10 21:31:22 +00:00
@classmethod
def search_rec_name(cls, name, clause):
""" search in rec_name
"""
return [('product.rec_name',) + tuple(clause[1:])]
@classmethod
def cron_update(cls):
""" update asset-rates
"""
pool = Pool()
Asset2 = pool.get('investment.asset')
2022-11-18 23:21:52 +00:00
OnlineSource = pool.get('investment.source')
2022-11-17 21:51:29 +00:00
for asset in Asset2.search([
('updtneeded', '=', True),
]):
2022-11-18 23:21:52 +00:00
OnlineSource.update_rate(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:
2022-11-22 21:43:28 +00:00
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:
2022-11-22 21:43:28 +00:00
values['updttime'] = None
super(Asset, cls).write(*args)
2022-11-09 20:49:33 +00:00
# end Asset