# -*- 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, If from trytond.report import Report from decimal import Decimal from datetime import time from sql.functions import CurrentDate, CurrentTimestamp from sql.conditionals import Case, Coalesce from sql import Literal digits_percent = 2 class Asset(ModelSQL, ModelView): 'Asset' __name__ = 'investment.asset' name = fields.Function(fields.Char(string='Name', readonly=True), 'on_change_with_name') company = fields.Many2One(string='Company', model_name='company.company', required=True, ondelete="RESTRICT") 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']) 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') 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='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') updttime = fields.Time(string='Time', states={ 'readonly': ~Bool(Eval('updtsource')), }, depends=['updtsource']) nextupdtate = fields.Function(fields.DateTime(string='Next Update', readonly=True), 'get_nextupdtates', searcher='search_nextupdtate') # 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') 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') 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') 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') 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') @classmethod def view_attributes(cls): return super().view_attributes() + [ ('/tree', 'visual', If(Eval('change_day1', 0) < 0, 'warning', If(Eval('change_day1', 0) > 0, 'success', '') )), ] @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 @classmethod def default_currency_digits(cls): """ default: 4 """ return 4 @classmethod def get_percentage_sql(cls, table_asset, name_lst): """ get table for percentages and dates generate adapted query """ pool = Pool() Rate = pool.get('investment.rate') tab_rate_today = Rate.__table__() rate_tab = { 'day1': {'tab': Rate.__table__(), 'day': 0}, 'month1': {'tab': Rate.__table__(), 'day': 30}, 'month3': {'tab': Rate.__table__(), 'day': 3*30}, 'month6': {'tab': Rate.__table__(), 'day': 6*30}, 'month12': {'tab': Rate.__table__(), 'day': 12*30}, } context = Transaction().context # create tables to query percentual changes for # 1 day, 30 days, 3 months, 6 months, 1 year 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, ) # create join for requested fields, to minimize database-load query = query_today for name in name_lst: query = query.join(rate_tab[name]['tab'], # select newest date from until 3 days older condition=(query_today.id==rate_tab[name]['tab'].asset) & \ (query_today.date > (rate_tab[name]['tab'].date + Literal(rate_tab[name]['day']))) & \ (query_today.date < (rate_tab[name]['tab'].date + Literal(rate_tab[name]['day'] + 3))), type_ = 'LEFT OUTER') # add select for requested fields to join select_lst = [ query_today.id, query_today.date, ] for name in name_lst: select_lst.append( Case( ((rate_tab[name]['tab'].rate != None) & (query_today.rate != None) & \ (rate_tab[name]['tab'].rate != Literal(0.0)), query_today.rate * Literal(100.0) / rate_tab[name]['tab'].rate - Literal(100.0)), else_ = None, ).as_(name)) order_by_lst = [query_today.id] order_by_lst.extend([ rate_tab[name]['tab'].date.desc for name in name_lst ]) query = query.select( *select_lst, distinct_on=[query_today.id], order_by=order_by_lst, ) # ~ 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() name_lst = [x[len('change_'):] for x in names] tab_percent = cls.get_percentage_sql(tab_asset, name_lst) query = tab_percent.select( tab_percent.id, *[getattr(tab_percent, x) for x in name_lst], 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} exp = Decimal(Decimal(1) / 10 ** digits_percent) for record in records: cnt1 = 1 for x in name_lst: result['change_%s' % x][record[0]] = record[cnt1].quantize(exp) \ if record[cnt1] is not None else None cnt1 += 1 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 @fields.depends('updtsource', 'updttime') def on_change_updtsource(self): """ clear time-fields """ if self.updtsource is None: self.updttime = None else : self.updttime = time(11, 30) @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 @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 @classmethod def get_next_update_datetime_sql(cls): """ get sql for datetime of next planned update """ pool = Pool() Asset = pool.get('investment.asset') Rate = pool.get('investment.rate') tab_asset = Asset.__table__() tab_rate = Rate.__table__() context = Transaction().context query_date = context.get('qdate', CurrentDate() - Literal(1)) query = tab_asset.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)) + \ tab_asset.updttime).as_('updttime'), distinct_on = [tab_asset.id], order_by = [tab_asset.id, tab_rate.date.desc], where=(tab_asset.updtsource != None), ) return query @classmethod def get_nextupdtates(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 = {'nextupdtate': updt} for n in names: result[n][id1] = r1[n] return result @classmethod def search_nextupdtate(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]] context = Transaction().context 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): """ 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 def get_rec_name(self, name): """ record name """ return '%(prod)s - %(rate)s %(curr)s/%(unit)s [%(date)s]' % { 'prod': getattr(self.product, 'rec_name', '-'), 'curr': getattr(self.currency, 'symbol', '-'), '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 '-', } @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') OnlineSource = pool.get('investment.source') query_time = context.get('qdatetime', CurrentTimestamp()) for asset in Asset2.search([ ('updttime', '<=', query_time), ]): 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: 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) # end Asset