Compare commits

...

45 commits

Author SHA1 Message Date
Frederik Jaeckel
882b8d2025 Version 6.0.7 2023-03-05 10:30:30 +01:00
Frederik Jaeckel
20c3cd5ed5 remove logging, add trytond.conf-settings 2023-03-05 10:26:39 +01:00
Frederik Jaeckel
6c5be8df2d book: optimize query for speed 2023-03-04 21:21:54 +01:00
Frederik Jaeckel
e6bb17f517 readme 2023-02-28 09:22:09 +01:00
Frederik Jaeckel
19e4a26676 fix: import + no symbol if None 2023-02-27 21:17:48 +01:00
Frederik Jaeckel
3512e87f71 Etikett ver 6.0.6 zum Änderungssatz 3f4bf4551378 hinzugefügt 2023-02-27 20:50:28 +01:00
Frederik Jaeckel
f0114b6f09 Version 6.0.6 2023-02-27 20:50:19 +01:00
Frederik Jaeckel
1e1e7d6b85 book:caching for line, settings: cache clear 2023-02-27 20:46:41 +01:00
Frederik Jaeckel
2b9a2ba07a book: add cache for asset-rates 2023-02-27 10:20:23 +01:00
Frederik Jaeckel
009f44816e book: new field 'purchase_amount', optimize form
speedup: indexes, caching
2023-02-26 22:51:24 +01:00
Frederik Jaeckel
d6ddb7a7d7 line: fix exception if missing config; book: optimize form 2023-02-23 10:35:30 +01:00
Frederik Jaeckel
e364560d0a book: optimized field 'yield_balance' 2023-02-22 21:24:58 +01:00
Frederik Jaeckel
8e5a730b36 book: added fields to book-form 2023-02-22 20:33:12 +01:00
Frederik Jaeckel
56961d2ea5 cashbook: yield-info prepared 2023-02-22 16:21:23 +01:00
Frederik Jaeckel
83950fc23a line: add help, fix form 2023-02-21 22:36:20 +01:00
Frederik Jaeckel
f9892379b1 line: tests... ok 2023-02-21 22:22:23 +01:00
Frederik Jaeckel
242f6d9578 line: field 'trade_fee' ok + test 2023-02-21 12:57:10 +01:00
Frederik Jaeckel
2176b84d52 line: fee tests ergänzt 2023-02-20 23:22:31 +01:00
Frederik Jaeckel
4e118afffb line: field 'asset_gainloss' ok + test 2023-02-18 20:55:04 +01:00
Frederik Jaeckel
6ab88d9fb5 line: gain/loss ok + test 2023-02-17 10:20:06 +01:00
Frederik Jaeckel
32eac5aa6c line: gain/loss field ... 2023-02-16 23:12:48 +01:00
Frederik Jaeckel
a47057900a line: updated get_gainloss_data_sql + todos 2023-02-16 16:59:50 +01:00
Frederik Jaeckel
9287db67ed add: field 'asset_gainloss' + searcher + test +todos 2023-02-15 22:34:52 +01:00
Frederik Jaeckel
5aad26da71 add: query for gain/loss 2023-02-14 23:03:12 +01:00
Frederik Jaeckel
71cdadb8ad line: negate amount if splitline=spout - tests 2023-02-14 22:27:25 +01:00
Frederik Jaeckel
d820fc7109 field trade_fee + asset_dividend ok + searcher + test 2023-02-12 22:01:07 +01:00
Frederik Jaeckel
c70d2fef7a line: add fields 'Fee' + 'Dividend' - todos 2023-02-12 00:09:56 +01:00
Frederik Jaeckel
43c9a1a2fa Etikett ver 6.0.5 zum Änderungssatz f0356df3abb0 hinzugefügt 2023-02-05 18:10:07 +01:00
Frederik Jaeckel
a7a9f9ef3d Version 6.0.5 2023-02-05 18:10:00 +01:00
Frederik Jaeckel
85d385901f line: fix write() - get 'bookingtype' from line if not in values 2023-02-05 11:35:55 +01:00
Frederik Jaeckel
cb81f788d6 Etikett ver 6.0.4 zum Änderungssatz 96f6e52d1e14 hinzugefügt 2023-02-01 14:51:54 +01:00
Frederik Jaeckel
e65c961e61 Version 6.0.4 2023-02-01 14:51:47 +01:00
Frederik Jaeckel
e8d69b64ae line/splitline: fixed selection of quantity-uom/digits 2023-02-01 14:29:48 +01:00
Frederik Jaeckel
6b630b7c20 Etikett ver 6.0.3 zum Änderungssatz 62e4472ad4a2 hinzugefügt 2023-01-30 09:27:52 +01:00
Frederik Jaeckel
9f12861b79 Version 6.0.3 2023-01-30 09:27:44 +01:00
Frederik Jaeckel
934d0fb207 reconciliation: use 'quantity_digits' for digits of start/end-quantity 2023-01-30 09:25:44 +01:00
Frederik Jaeckel
9edd2d4a95 line: add performance values to line-form 2023-01-29 23:16:22 +01:00
Frederik Jaeckel
3243fbf844 Etikett ver 6.0.2 zum Änderungssatz ae7678260358 hinzugefügt 2023-01-28 13:13:20 +01:00
Frederik Jaeckel
c9d6bf21a5 Version 6.0.2 2023-01-28 13:13:14 +01:00
Frederik Jaeckel
a315f4e768 cashbook: view current_value/-ref on chasbooks with btype=None if
asset-cashbooks are below
2023-01-28 13:03:24 +01:00
Frederik Jaeckel
674f72476e book: asset-query ausgelagert 2023-01-24 21:25:29 +01:00
Frederik Jaeckel
12e088f79a line: fix transfer between cash-/asset-book with zero quantity 2023-01-23 22:29:41 +01:00
Frederik Jaeckel
bbf94c95df line: fix rec_name 2023-01-22 10:28:09 +01:00
Frederik Jaeckel
29b74d0600 Etikett ver 6.0.1 zum Änderungssatz 67d3f883961c hinzugefügt 2023-01-21 19:18:44 +01:00
Frederik Jaeckel
269e744b8f Version 6.0.1 2023-01-21 19:18:36 +01:00
23 changed files with 3249 additions and 108 deletions

View file

@ -11,9 +11,50 @@ Requires
========
- Tryton 6.0
How to
======
The module extends the cash accounts in mds-cashbook with
the ability to store quantities. You are free to choose the
unit of measurement. It uses the current price data
from the mds-investment module.
You can monitor trading fees, dividends and sales profits.
Changes
=======
*6.0.7 - 05.03.2023*
- updt: optimize queries for speed, optimize caching
- add: trytond.conf-settings
*6.0.6 - 27.02.2023*
- add: caching
- add: performance and yield info
*6.0.5 - 05.02.2023*
- fix: line rewrite values
*6.0.4 - 01.02.2023*
- fix: selection of quantity-uom/digits on line/spitline
*6.0.3 - 30.01.2023*
- add: performance-tab to line-form
- fix: digits of start/end-quantity in reconciliation
*6.0.2 - 28.01.2023*
- fix: transfer between cash-/asset-book with zero quantity
- add: view value/percent on chasbooks with btype=None
*6.0.1 - 21.01.2023*
- works
*6.0.0 - 20.12.2022*
- init

View file

@ -9,13 +9,17 @@ from .book import Book
from .reconciliation import Reconciliation
from .line import Line
from .splitline import SplitLine
from .assetsetting import AssetSetting
from .asset import AssetRate
def register():
Pool.register(
AssetRate,
Type,
Book,
Line,
SplitLine,
Reconciliation,
AssetSetting,
module='cashbook_investment', type_='model')

48
asset.py Normal file
View file

@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds.de for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.pool import Pool, PoolMeta
CACHEKEY_ASSETRATE = 'assetrate-%s'
class AssetRate(metaclass=PoolMeta):
__name__ = 'investment.rate'
@classmethod
def create(cls, vlist):
""" update cache-value
"""
MemCache = Pool().get('cashbook.memcache')
records = super(AssetRate, cls).create(vlist)
for rate in records:
MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate)
return records
@classmethod
def write(cls, *args):
""" update cache-value
"""
MemCache = Pool().get('cashbook.memcache')
super(AssetRate, cls).write(*args)
actions = iter(args)
for rates, values in zip(actions, actions):
for rate in rates:
MemCache.record_update(CACHEKEY_ASSETRATE % rate.asset.id, rate)
@classmethod
def delete(cls, records):
""" set cache to None
"""
MemCache = Pool().get('cashbook.memcache')
for record in records:
MemCache.record_update(CACHEKEY_ASSETRATE % record.asset.id, None)
super(AssetRate, cls).delete(records)
# end

34
assetsetting.py Normal file
View file

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# This file is part of the cashbook-module from m-ds.de for Tryton.
# The COPYRIGHT file at the top level of this repository contains the
# full copyright notices and license terms.
from trytond.model import ModelSingleton, ModelView, ModelSQL, fields
from trytond.pool import Pool
class AssetSetting(ModelSingleton, ModelSQL, ModelView):
'Asset setting'
__name__ = 'cashbook.assetconf'
fee_category = fields.Many2One(string='Fee category',
model_name='cashbook.category', ondelete='RESTRICT',
help='Category for fees when trading assets.')
dividend_category = fields.Many2One(string='Dividend category',
model_name='cashbook.category', ondelete='RESTRICT',
help='Category for dividend paid out.')
gainloss_book = fields.Many2One(string='Profit/Loss Cashbook',
model_name='cashbook.book', ondelete='RESTRICT',
help='Profit and loss on sale of assets are recorded in the cash book.')
@classmethod
def write(cls, *args):
""" clear cache-values
"""
MemCache = Pool().get('cashbook.memcache')
MemCache._cashbook_value_cache.clear_all()
super(AssetSetting, cls).write(*args)
# end AssetSetting

53
assetsetting.xml Normal file
View file

@ -0,0 +1,53 @@
<?xml version="1.0"?>
<!-- This file is part of the cashbook-module from m-ds 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="assetconf_view_form">
<field name="model">cashbook.assetconf</field>
<field name="type">form</field>
<field name="name">assetconf_form</field>
</record>
<record model="ir.action.act_window" id="act_assetconf_form">
<field name="name">Asset setting</field>
<field name="res_model">cashbook.assetconf</field>
</record>
<record model="ir.action.act_window.view" id="act_assetconf_form-1">
<field name="sequence" eval="10"/>
<field name="view" ref="assetconf_view_form"/>
<field name="act_window" ref="act_assetconf_form"/>
</record>
<!-- permission -->
<!-- anon: deny all -->
<record model="ir.model.access" id="asset_conf-anon">
<field name="model" search="[('model', '=', 'cashbook.assetconf')]"/>
<field name="perm_read" eval="False"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<!-- group_cashbook_admin: read/write -->
<record model="ir.model.access" id="asset_conf-group_cashbook_admin">
<field name="model" search="[('model', '=', 'cashbook.assetconf')]"/>
<field name="group" ref="cashbook.group_cashbook_admin"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="True"/>
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<!-- cashbook: read only -->
<record model="ir.model.access" id="asset_conf-group_cashbook">
<field name="model" search="[('model', '=', 'cashbook.assetconf')]"/>
<field name="group" ref="cashbook.group_cashbook"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
</data>
</tryton>

463
book.py
View file

@ -9,16 +9,35 @@ from trytond.pyson import Eval, Or, Len, Bool, If
from trytond.modules.cashbook.book import STATES2, DEPENDS2
from trytond.transaction import Transaction
from trytond.report import Report
from trytond.config import config
from decimal import Decimal
from datetime import timedelta
from sql import Literal
from sql.functions import CurrentDate
from sql.aggregate import Sum
from sql.conditionals import Case, Coalesce
from trytond.modules.cashbook.model import sub_ids_hierarchical,\
CACHEKEY_CURRENCY, AnyInArray
from .asset import CACHEKEY_ASSETRATE
# enable/disable caching of cachekey for 'currency.rate'
if config.get('cashbook', 'cache_currency', default='yes').lower() in ['yes', '1', 'true']:
ENA_CURRKEY = True
else :
ENA_CURRKEY = False
# enable/disable caching of cachekey for 'currency.rate'
if config.get('cashbook', 'cache_asset', default='yes').lower() in ['yes', '1', 'true']:
ENA_ASSETKEY = True
else :
ENA_ASSETKEY = False
class Book(SymbolMixin, metaclass=PoolMeta):
__name__ = 'cashbook.book'
asset = fields.Many2One(string='Asset',
asset = fields.Many2One(string='Asset', select=True,
model_name='investment.asset', ondelete='RESTRICT',
states={
'required': Eval('feature', '') == 'asset',
@ -45,7 +64,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
asset_uomcat = fields.Function(fields.Many2One(string='UOM Category',
readonly=True, model_name='product.uom.category',
states={'invisible': True}), 'on_change_with_asset_uomcat')
quantity_uom = fields.Many2One(string='UOM',
quantity_uom = fields.Many2One(string='UOM', select=True,
model_name='product.uom', ondelete='RESTRICT',
domain=[
('category.id', '=', Eval('asset_uomcat', -1)),
@ -81,18 +100,18 @@ class Book(SymbolMixin, metaclass=PoolMeta):
help='Valuation of the investment based on the current stock market price.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'invisible': Eval('show_performance', False) == False,
}, depends=['currency_digits', 'show_performance']),
'get_asset_quantity')
current_value_ref = fields.Function(fields.Numeric(string='Value (Ref.)',
help='Valuation of the investment based on the current stock exchange price, converted into the company currency.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Or(
Eval('feature', '') != 'asset',
Eval('show_performance', False) == False,
~Bool(Eval('company_currency', -1)),
),
}, depends=['currency_digits', 'feature', 'company_currency']),
}, depends=['currency_digits', 'show_performance', 'company_currency']),
'get_asset_quantity')
# performance
@ -100,26 +119,80 @@ class Book(SymbolMixin, metaclass=PoolMeta):
help='Difference between acquisition value and current value',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_asset_quantity')
'invisible': Eval('show_performance', False) == False,
}, depends=['currency_digits', 'show_performance']), 'get_asset_quantity')
diff_percent = fields.Function(fields.Numeric(string='Percent',
help='percentage performance since acquisition',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_asset_quantity')
'invisible': Eval('show_performance', False) == False,
}, depends=['currency_digits', 'show_performance']), 'get_asset_quantity')
show_performance = fields.Function(fields.Boolean(string='Performance',
readonly=True), 'on_change_with_show_performance')
current_rate = fields.Function(fields.Numeric(string='Rate',
help='Rate per unit of investment based on current stock exchange price.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_asset_quantity')
purchase_amount = fields.Function(fields.Numeric(string='Purchase Amount',
help='Total purchase amount, from shares and fees.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_asset_quantity')
# yield
yield_dividend_total = fields.Function(fields.Numeric(string='Dividend',
help='Total dividends received',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_dividend_12m = fields.Function(fields.Numeric(string='Dividend 1y',
help='Dividends received in the last twelve months',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_fee_total = fields.Function(fields.Numeric(string='Trade Fee',
help='Total trade fees payed',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_fee_12m = fields.Function(fields.Numeric(string='Trade Fee 1y',
help='Trade fees payed in the last twelve month',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_sales = fields.Function(fields.Numeric(string='Sales',
help='Total profit or loss on sale of shares.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_sales_12m = fields.Function(fields.Numeric(string='Sales 1y',
help='Total profit or loss on sale of shares in the last twelve month.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']), 'get_yield_data')
yield_balance = fields.Function(fields.Numeric(string='Total Yield',
help='Total income: price gain + dividends + sales gains - fees',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'on_change_with_yield_balance')
@classmethod
def view_attributes(cls):
return super(Book, cls).view_attributes() + [
('/tree', 'visual',
If(Eval('feature', '') == 'asset',
If(Eval('show_performance', False) == True,
If(Eval('diff_percent', 0) < 0, 'danger',
If(Eval('diff_percent', 0) > 0, 'success', '')
), '')
@ -151,17 +224,145 @@ class Book(SymbolMixin, metaclass=PoolMeta):
"""
return 4
@fields.depends('yield_sales', 'yield_dividend_total', 'diff_amount')
def on_change_with_yield_balance(self, name=None):
""" calculate yield total
fee is already contained in 'diff_amount'
"""
sum_lst = [self.diff_amount, self.yield_dividend_total, self.yield_sales]
sum2 = sum([x for x in sum_lst if x is not None])
return sum2
@classmethod
def get_asset_quantity(cls, cashbooks, names):
""" get quantities
"""
def get_yield_data_sql(cls, date_from=None, date_to=None):
""" collect yield data
"""
pool = Pool()
Line = pool.get('cashbook.line')
Currency = pool.get('currency.currency')
(tab_line1, tab_line_yield) = Line.get_yield_data_sql()
(tab_line2, tab_line_gainloss) = Line.get_gainloss_data_sql()
tab_book = cls.__table__()
tab_line = Line.__table__()
tab_cur = Currency.__table__()
where = Literal(True)
if date_from:
where &= tab_line.date >= date_from
if date_to:
where &= tab_line.date <= date_to
query = tab_book.join(tab_line,
condition=tab_line.cashbook==tab_book.id,
).join(tab_cur,
condition=tab_cur.id==tab_book.currency,
).join(tab_line_yield,
condition=tab_line_yield.id==tab_line.id,
).join(tab_line_gainloss,
condition=tab_line_gainloss.id==tab_line.id,
).select(
tab_book.id,
Sum(tab_line_yield.fee).as_('fee'),
Sum(tab_line_yield.dividend).as_('dividend'),
Sum(tab_line_gainloss.gainloss).as_('gainloss'),
tab_cur.digits.as_('currency_digits'),
group_by=[tab_book.id, tab_cur.digits],
where=where
)
return (tab_book, query)
@classmethod
def get_yield_data(cls, cashbooks, names):
""" collect yield data
"""
pool = Pool()
CashBook = pool.get('cashbook.book')
IrDate = pool.get('ir.date')
MemCache = pool.get('cashbook.memcache')
cursor = Transaction().connection.cursor()
context = Transaction().context
result = {
x:{y.id: Decimal('0.0') for y in cashbooks}
for x in ['yield_fee_total', 'yield_dividend_total',
'yield_sales', 'yield_fee_12m', 'yield_dividend_12m',
'yield_sales_12m']}
def quantize_val(value, digits):
""" quantize...
"""
return (
value or Decimal('0.0')
).quantize(Decimal(str(1/10 ** digits)))
query_date = context.get('date', IrDate.today())
cache_keys = {
x.id: MemCache.get_key_by_record(
name = 'get_yield_data',
record = x,
query = [{
'model': 'cashbook.line',
'query': [('cashbook.parent', 'child_of', [x.id])],
}, {
'model': 'currency.currency.rate',
'query': [('currency.id', '=', x.currency.id)],
'cachekey' if ENA_CURRKEY else 'disabled': CACHEKEY_CURRENCY % x.currency.id,
}, {
'model': 'investment.rate',
'query': [('asset.id', '=', x.asset.id)],
'cachekey' if ENA_ASSETKEY else 'disabled': CACHEKEY_ASSETRATE % x.asset.id,
} if x.asset is not None else {}],
addkeys = [query_date.isoformat()])
for x in cashbooks
}
# read from cache
(todo_cashbook, result) = MemCache.read_from_cache(
cashbooks, cache_keys, names, result)
if len(todo_cashbook) == 0:
return result
# results for 'total'
records_total = []
records_12m = []
if len(todo_cashbook) > 0:
(tab_book1, query_total) = cls.get_yield_data_sql()
query_total.where &= tab_book1.id.in_([x.id for x in todo_cashbook])
cursor.execute(*query_total)
records_total = cursor.fetchall()
# results for 12 months
(tab_book2, query_12m) = cls.get_yield_data_sql(
date_to = query_date,
date_from = query_date - timedelta(days=365),
)
query_12m.where &= tab_book2.id.in_([x.id for x in todo_cashbook])
cursor.execute(*query_12m)
records_12m = cursor.fetchall()
for record in records_total:
result['yield_fee_total'][record[0]] = quantize_val(record[1], record[4])
result['yield_dividend_total'][record[0]] = quantize_val(record[2], record[4])
result['yield_sales'][record[0]] = quantize_val(record[3], record[4])
for record in records_12m:
result['yield_fee_12m'][record[0]] = quantize_val(record[1], record[4])
result['yield_dividend_12m'][record[0]] = quantize_val(record[2], record[4])
result['yield_sales_12m'][record[0]] = quantize_val(record[3], record[4])
# store to cache
MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook)
return {x:result[x] for x in names}
@classmethod
def get_asset_quantity_sql(cls):
""" get table of asset and its value, rate, ...
"""
pool = Pool()
CBook = pool.get('cashbook.book')
BookType = pool.get('cashbook.type')
Line = pool.get('cashbook.line')
Asset = pool.get('investment.asset')
Currency = pool.get('currency.currency')
Uom = pool.get('product.uom')
tab_book = CBook.__table__()
tab_type = BookType.__table__()
tab_line = Line.__table__()
@ -169,13 +370,10 @@ class Book(SymbolMixin, metaclass=PoolMeta):
tab_asset = Asset.__table__()
(tab_rate, tab2) = Asset.get_rate_data_sql()
(tab_balance, tab2) = CBook.get_balance_of_cashbook_sql()
cursor = Transaction().connection.cursor()
(tab_line_yield, query_yield) = Line.get_yield_data_sql()
context = Transaction().context
result = {x:{y.id: None for y in cashbooks} for x in names}
query_date = context.get('qdate', CurrentDate())
company_currency = CBook.default_currency()
query = tab_book.join(tab_line,
condition=(tab_book.id==tab_line.cashbook),
).join(tab_type,
@ -184,6 +382,8 @@ class Book(SymbolMixin, metaclass=PoolMeta):
condition=tab_book.currency==tab_cur.id,
).join(tab_asset,
condition=tab_book.asset==tab_asset.id,
).join(query_yield,
condition=query_yield.id==tab_line.id,
).join(tab_balance,
condition=tab_book.id==tab_balance.cashbook,
type_ = 'LEFT OUTER',
@ -204,56 +404,223 @@ class Book(SymbolMixin, metaclass=PoolMeta):
tab_asset.uom, # 6
tab_book.quantity_uom, # 7
tab_asset.currency.as_('asset_currency'), #8
Coalesce(tab_balance.balance, Decimal('0.0')).as_('balance'), #9
(
Sum(query_yield.fee) + tab_balance.balance
).as_('purchase_amount'), #9
group_by=[tab_book.id, tab_rate.rate,
tab_book.currency, tab_cur.digits, tab_asset.uom,
tab_book.quantity_uom, tab_asset.currency,
tab_balance.balance],
where=tab_book.id.in_([x.id for x in cashbooks]) & \
(tab_type.feature == 'asset'),
where=(tab_type.feature == 'asset'),
)
cursor.execute(*query)
records = cursor.fetchall()
return (query, tab_book)
for record in records:
@classmethod
def get_asset_quantity(cls, cashbooks, names):
""" get quantities
field: quantity, quantity_all, current_value,
current_value_ref, diff_amount, diff_percent,
current_rate, purchase_amount
"""
pool = Pool()
CBook = pool.get('cashbook.book')
Uom = pool.get('product.uom')
Currency = pool.get('currency.currency')
IrDate = pool.get('ir.date')
MemCache = pool.get('cashbook.memcache')
cursor = Transaction().connection.cursor()
context = Transaction().context
(query, tab_book) = cls.get_asset_quantity_sql()
company_currency = CBook.default_currency()
result = {
x:{y.id: None for y in cashbooks}
for x in ['quantity', 'quantity_all', 'current_value',
'current_value_ref', 'diff_amount', 'diff_percent',
'current_rate', 'purchase_amount', 'purchase_amount_ref',
'digits']
}
cache_keys = {
x.id: MemCache.get_key_by_record(
name = 'get_asset_quantity',
record = x,
query = [{
'model': 'cashbook.line',
'query': [('cashbook.parent', 'child_of', [x.id])],
}, {
'model': 'currency.currency.rate',
'query': [('currency.id', '=', x.currency.id)],
'cachekey' if ENA_CURRKEY else 'disabled': CACHEKEY_CURRENCY % x.currency.id,
}, {
'model': 'investment.rate',
'query': [('asset.id', '=', x.asset.id)],
'cachekey' if ENA_ASSETKEY else 'disabled': CACHEKEY_ASSETRATE % x.asset.id,
} if x.asset is not None else {}],
addkeys=[
str(company_currency),
str(context.get('qdate', IrDate.today()).toordinal()),
])
for x in cashbooks
}
# read from cache
(todo_cashbook, result) = MemCache.read_from_cache(
cashbooks, cache_keys, names, result)
if len(todo_cashbook) == 0:
return result
def values_from_record(rdata):
""" compute values for record
"""
# uom-factor
if record[6] == record[7]:
if rdata[6] == rdata[7]:
uom_factor = Decimal('1.0')
else :
uom_factor = Decimal(
Uom.compute_qty(Uom(record[6]), 1.0, Uom(record[7]), round=False)
Uom.compute_qty(Uom(rdata[6]), 1.0, Uom(rdata[7]), round=False)
)
current_value = Currency.compute(
record[8],
record[3] * record[1] / uom_factor,
record[4]
rdata[8],
rdata[3] * rdata[1] / uom_factor,
rdata[4]
)
values = {
'quantity': record[1],
'quantity_all': record[2],
return (record[0], {
'quantity': rdata[1],
'quantity_all': rdata[2],
'current_value': current_value,
'current_value_ref': Currency.compute(
record[8],
record[3] * record[1] / uom_factor,
company_currency if company_currency is not None else record[8],
rdata[8],
rdata[3] * rdata[1] / uom_factor,
company_currency if company_currency is not None else rdata[8],
),
'diff_amount': current_value - record[9],
'diff_amount': current_value - rdata[9],
'diff_percent': (
Decimal('100.0') * current_value / \
record[9] - Decimal('100.0')
).quantize(Decimal(str(1/10**record[5]))) \
if record[9] != Decimal('0.0') else None,
rdata[9] - Decimal('100.0')
).quantize(Decimal(str(1/10**rdata[5]))) \
if rdata[9] != Decimal('0.0') else None,
'current_rate': (
current_value / record[1]
).quantize(Decimal(str(1/10**record[5]))) \
if record[1] != Decimal('0.0') else None,
}
current_value / rdata[1]
).quantize(Decimal(str(1/10**rdata[5]))) \
if rdata[1] != Decimal('0.0') else None,
'purchase_amount': record[9].quantize(Decimal(str(1/10**rdata[5]))),
'purchase_amount_ref': Currency.compute(
rdata[4],
record[9],
company_currency if company_currency is not None else rdata[4],
),
})
for name in names:
result[name][record[0]] = values[name]
return result
ids_assetbooks = [x.id for x in cashbooks if x.btype is not None]
ids_nonebtypes = [x.id for x in cashbooks if x.btype is None]
# get values of asset-cashbooks in 'cashbooks' of type=asset
if len(ids_assetbooks) > 0:
query.where &= tab_book.id.in_(ids_assetbooks)
cursor.execute(*query)
records = cursor.fetchall()
for record in records:
(book_id, values) = values_from_record(record)
for name in values.keys():
result[name][book_id] = values[name]
# add aggregated values of cashbooks without type
aggr_names = ['current_value', 'current_value_ref',
'diff_amount', 'diff_percent']
queried_names = list(set(aggr_names).intersection(set(names)))
if (len(queried_names) > 0) and (len(ids_nonebtypes) > 0):
# query all subordered asset-cashbooks to get values for
# cashbooks without type
(tab_quantity, tab_book) = cls.get_asset_quantity_sql()
tab_subids = sub_ids_hierarchical('cashbook.book')
query = tab_book.join(tab_subids,
condition=tab_book.id==tab_subids.parent,
).join(tab_quantity,
condition=tab_quantity.id==AnyInArray(tab_subids.subids),
).select(
tab_book.id,
Sum(tab_quantity.quantity), # 1
Sum(tab_quantity.quantity_all), # 2
tab_quantity.rate, # 3
tab_quantity.currency, # 4
tab_quantity.currency_digits, # 5
tab_quantity.uom, # 6
tab_quantity.quantity_uom, # 7
tab_quantity.asset_currency, # 8
Sum(tab_quantity.purchase_amount), # 9
tab_book.currency.as_('currency_book'), # 10
where=tab_book.id.in_(ids_nonebtypes),
group_by=[tab_book.id, tab_quantity.rate,
tab_quantity.currency, tab_quantity.currency_digits,
tab_quantity.uom, tab_quantity.quantity_uom,
tab_quantity.asset_currency,tab_book.currency],
)
cursor.execute(*query)
records = cursor.fetchall()
for record in records:
(book_id, values) = values_from_record(record)
for name in ['current_value', 'diff_amount',
'current_value_ref', 'purchase_amount_ref']:
if result[name][book_id] is None:
result[name][book_id] = Decimal('0.0')
value = Decimal('0.0')
if name == 'current_value':
value = Currency.compute(
company_currency if company_currency is not None else record[4],
values['current_value_ref'],
record[10],
)
elif name == 'diff_amount':
value = Currency.compute(
company_currency if company_currency is not None else record[4],
values['current_value_ref'] - values['purchase_amount_ref'],
record[10],
)
elif name in ['current_value_ref', 'purchase_amount_ref']:
value = values[name]
result['digits'][book_id] = record[5]
result[name][book_id] += value
# diff_percent
for id_book in ids_nonebtypes:
c_val = result['current_value_ref'][id_book]
p_amount = result['purchase_amount_ref'][id_book]
digits = result['digits'][id_book]
if (p_amount == Decimal('0.0')) or (p_amount is None) or (c_val is None):
continue
result['diff_percent'][id_book] = (
Decimal('100.0') * c_val / p_amount - Decimal('100.0')
).quantize(Decimal(str(1/10 ** digits)))
result['digits'][id_book] = None
# store to cache
MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook)
return {x:result[x] for x in names}
@fields.depends('id')
def on_change_with_show_performance(self, name=None):
""" return True if current or subordered cashbooks
are of type=asset
"""
Book2 = Pool().get('cashbook.book')
if Book2.search_count([
('btype.feature', '=', 'asset'),
('parent', 'child_of', [self.id]),
]) > 0:
return True
return False
@fields.depends('id')
def on_change_with_asset_symbol(self, name=None):

8
docs/settings.txt Normal file
View file

@ -0,0 +1,8 @@
settings in tytond.conf
[cashbook]
# Enables caching of the cache key for asset rates.
# Reduces the time required to determine the cache
# key for asset values.
# default: yes
cache_asset = yes

416
line.py
View file

@ -4,12 +4,15 @@
# full copyright notices and license terms.
from decimal import Decimal
from sql.conditionals import Coalesce, Case
from sql.aggregate import Sum
from trytond.model import fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Or, If, And
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.report import Report
from trytond.transaction import Transaction
from trytond.modules.cashbook.line import STATES, DEPENDS
from .mixin import SecondUomMixin
@ -85,6 +88,317 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
string='has quantity', readonly=True, states={'invisible': True}),
'on_change_with_splitline_has_quantity')
# performance
current_value = fields.Function(fields.Numeric(string='Value',
help='Valuation of the investment based on the current stock market price.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'on_change_with_current_value')
diff_amount = fields.Function(fields.Numeric(string='Difference',
help='Difference between acquisition value and current value',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'on_change_with_diff_amount')
diff_percent = fields.Function(fields.Numeric(string='Percent',
help='percentage performance since acquisition',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states = {
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'on_change_with_diff_percent')
trade_fee = fields.Function(fields.Numeric(string='Fee',
help='Trading fee for the current booking line.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states = {
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_yield_data', searcher='search_trade_fee')
asset_dividend = fields.Function(fields.Numeric(string='Dividend',
help='Dividend received at the current booking line.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states = {
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_yield_data', searcher='search_asset_dividend')
asset_gainloss = fields.Function(fields.Numeric(string='Profit/Loss',
help='Profit or loss on sale on the current booking line.',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states = {
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_yield_data', searcher='search_asset_gainloss')
@classmethod
def get_gainloss_data_sql(cls):
""" query for gain/loss on sell of shares
"""
pool = Pool()
AssetSetting = pool.get('cashbook.assetconf')
SplitLine = pool.get('cashbook.split')
tab_line = cls.__table__()
tab_mvsp_counterpart = cls.__table__()
tab_mvsp_local = cls.__table__()
tab_mvmv_counterpart = cls.__table__()
tab_spmv_counterpart = cls.__table__()
tab_mv_spline = SplitLine.__table__()
cfg1 = AssetSetting.get_singleton()
gainloss_book = getattr(getattr(cfg1, 'gainloss_book', None), 'id', None)
tab_assetline = cls.search([
('cashbook.btype.feature', '=', 'asset'),
], query=True)
query = tab_line.join(tab_assetline,
condition=(tab_assetline.id==tab_line.id),
).join(tab_mvsp_counterpart,
# [MV-SP] transfer booking, select counterpart [1] - a split-booking
condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \
((tab_line.reference == tab_mvsp_counterpart.id) | \
(tab_line.id == tab_mvsp_counterpart.reference)) & \
(tab_mvsp_counterpart.bookingtype.in_(['spin', 'spout'])),
type_ = 'LEFT OUTER',
).join(tab_mv_spline,
# [MV-SP] line is linked to split-booking-line of counterpart [1]
condition=(tab_mv_spline.line == tab_mvsp_counterpart.id) & \
(tab_mv_spline.splittype == 'tr') & \
(tab_mv_spline.booktransf != None) & \
(tab_mv_spline.booktransf == gainloss_book),
type_ = 'LEFT OUTER',
).join(tab_spmv_counterpart,
# [SP-MV] split booking, select counterpart [1] - a transfer-booking
condition=tab_line.bookingtype.in_(['spin', 'spout']) & \
((tab_line.reference == tab_spmv_counterpart.id) | \
(tab_line.id == tab_spmv_counterpart.reference)) & \
tab_spmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & \
(tab_spmv_counterpart.cashbook == gainloss_book),
type_ = 'LEFT OUTER',
).join(tab_mvmv_counterpart,
# [MV-MV] transfer booking
condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \
((tab_mvmv_counterpart.reference == tab_line.id) | \
(tab_mvmv_counterpart.id == tab_line.reference)) & \
tab_mvmv_counterpart.bookingtype.in_(['mvin', 'mvout']) & \
(tab_mvmv_counterpart.cashbook == gainloss_book),
type_ = 'LEFT OUTER',
).select(
tab_line.id,
(Coalesce(
tab_mvmv_counterpart.credit - tab_mvmv_counterpart.debit,
Case(
(tab_line.bookingtype == 'mvin', tab_mv_spline.amount),
(tab_line.bookingtype == 'mvout', tab_mv_spline.amount * Decimal('-1.0')),
),
Case(
(tab_mvsp_counterpart.cashbook == gainloss_book,
tab_line.debit - tab_line.credit),
),
tab_spmv_counterpart.credit - tab_spmv_counterpart.debit,
Decimal('0.0'),
) * Decimal('-1.0')).as_('gainloss'),
tab_line.cashbook,
)
return (tab_line, query)
@classmethod
def get_yield_data_sql(cls):
""" query for fee, dividend, gain/loss
"""
pool = Pool()
AssetSetting = pool.get('cashbook.assetconf')
SplitLine = pool.get('cashbook.split')
tab_line = cls.__table__()
tab_inout_fee = cls.__table__()
tab_inout_divi = cls.__table__()
tab_mv_counterpart = cls.__table__()
tab_mv_spline_fee = SplitLine.__table__()
tab_mv_spline_divi = SplitLine.__table__()
tab_spline_fee = SplitLine.__table__()
tab_spline_divi = SplitLine.__table__()
cfg1 = AssetSetting.get_singleton()
fee_category = getattr(getattr(cfg1, 'fee_category', None), 'id', None)
dividend_category = getattr(getattr(cfg1, 'dividend_category', None), 'id', None)
tab_assetline = cls.search([
('cashbook.btype.feature', '=', 'asset'),
], query=True)
query = tab_line.join(tab_assetline,
condition=(tab_assetline.id==tab_line.id),
).join(tab_inout_fee,
# [INOUT] fee, local booked
condition=(tab_inout_fee.id==tab_line.id) & \
tab_inout_fee.bookingtype.in_(['in', 'out']) & \
(tab_inout_fee.category != None) & \
(tab_inout_fee.category == fee_category),
type_ = 'LEFT OUTER',
).join(tab_inout_divi,
# [INOUT] dividend, local booked
condition=(tab_inout_divi.id==tab_line.id) & \
tab_inout_divi.bookingtype.in_(['in', 'out']) & \
(tab_inout_divi.category != None) & \
(tab_inout_divi.category == dividend_category),
type_ = 'LEFT OUTER',
).join(tab_mv_counterpart,
# [MV] transfer booking, select counterpart [1] - a split-booking
condition=tab_line.bookingtype.in_(['mvin', 'mvout']) & \
((tab_line.reference == tab_mv_counterpart.id) | \
(tab_line.id == tab_mv_counterpart.reference)) & \
(tab_mv_counterpart.bookingtype.in_(['spin', 'spout'])),
type_ = 'LEFT OUTER',
).join(tab_mv_spline_fee,
# [MV] fee-line is linked to split-booking-line of counterpart [1]
condition=(tab_mv_spline_fee.line == tab_mv_counterpart.id) & \
(tab_mv_spline_fee.splittype == 'cat') & \
(tab_mv_spline_fee.category != None) & \
(tab_mv_spline_fee.category == fee_category),
type_ = 'LEFT OUTER',
).join(tab_mv_spline_divi,
# [MV] dividend-line is linked to split-booking-line of counterpart [1]
condition=(tab_mv_spline_divi.line == tab_mv_counterpart.id) & \
(tab_mv_spline_divi.splittype == 'cat') & \
(tab_mv_spline_divi.category != None) & \
(tab_mv_spline_divi.category == dividend_category),
type_ = 'LEFT OUTER',
).join(tab_spline_fee,
# [SP] fee, split booking
condition=(tab_spline_fee.line == tab_line.id) & \
tab_line.bookingtype.in_(['spin', 'spout']) & \
(tab_spline_fee.splittype == 'cat') & \
(tab_spline_fee.category != None) & \
(tab_spline_fee.category == fee_category),
type_ = 'LEFT OUTER',
).join(tab_spline_divi,
# [SP] dividend, split booking
condition=(tab_spline_divi.line == tab_line.id) & \
tab_line.bookingtype.in_(['spin', 'spout']) & \
(tab_spline_divi.splittype == 'cat') & \
(tab_spline_divi.category != None) & \
(tab_spline_divi.category == dividend_category),
type_ = 'LEFT OUTER',
).select(
tab_line.id,
Sum(Coalesce(
# out-booking, positive amount = fee positive
tab_inout_fee.debit - tab_inout_fee.credit,
# a category-out on splitbooking as counterpart of
# transfer = fee is positive
tab_mv_spline_fee.amount,
Case(
(tab_line.bookingtype == 'spin', tab_spline_fee.amount * Decimal('-1.0')),
(tab_line.bookingtype == 'spout', tab_spline_fee.amount),
),
Decimal('0.0'),
)).as_('fee'),
Sum(Coalesce(
tab_inout_divi.credit - tab_inout_divi.debit,
tab_mv_spline_divi.amount,
Case(
(tab_line.bookingtype == 'spin', tab_spline_divi.amount),
(tab_line.bookingtype == 'spout', tab_spline_divi.amount * Decimal('-1.0')),
),
Decimal('0.0'),
)).as_('dividend'),
tab_line.cashbook,
group_by=[tab_line.id, tab_line.cashbook],
)
return (tab_line, query)
@classmethod
def search_trade_fee(cls, name, clause):
""" search for fees
"""
Operator = fields.SQL_OPERATORS[clause[1]]
(tab_line, tab_query) = cls.get_yield_data_sql()
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.fee, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def search_asset_dividend(cls, name, clause):
""" search for dividends
"""
Operator = fields.SQL_OPERATORS[clause[1]]
(tab_line, tab_query) = cls.get_yield_data_sql()
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.dividend, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def search_asset_gainloss(cls, name, clause):
""" search for profit/loss
"""
Operator = fields.SQL_OPERATORS[clause[1]]
(tab_line, tab_query) = cls.get_gainloss_data_sql()
query = tab_query.select(
tab_query.id,
where=Operator(tab_query.gainloss, clause[2]),
)
return [('id', 'in', query)]
@classmethod
def get_yield_data(cls, lines, names):
""" collect data for fee, dividend, gain/loss per line
"""
Line2 = Pool().get('cashbook.line')
cursor = Transaction().connection.cursor()
def quantize_val(value, line):
""" quantize...
"""
return (
value or Decimal('0.0')
).quantize(Decimal(str(1/10**line.currency_digits)))
result = {x:{y.id: None for y in lines} for x in names}
# read fee, dividend
name_set = set({'trade_fee', 'asset_dividend'}).intersection(set(names))
if len(name_set) > 0:
(tab_line, query) = cls.get_yield_data_sql()
query.where = tab_line.id.in_([x.id for x in lines])
cursor.execute(*query)
records = cursor.fetchall()
for record in records:
line = Line2(record[0])
values = {
'trade_fee': quantize_val(record[1], line),
'asset_dividend': quantize_val(record[2], line),
}
for name in list(name_set):
result[name][record[0]] = values[name]
# read asset_gainloss
if 'asset_gainloss' in names:
(tab_line, query) = cls.get_gainloss_data_sql()
query.where = tab_line.id.in_([x.id for x in lines])
cursor.execute(*query)
records = cursor.fetchall()
for record in records:
line = Line2(record[0])
result['asset_gainloss'][record[0]] = quantize_val(record[1], line)
return result
def get_rec_name(self, name):
""" add quantities - if its a asset-cashbook
"""
@ -95,7 +409,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
recname += '|%(quantity)s %(uom_symbol)s' % {
'quantity': Report.format_number(credit - debit,
lang=None, digits=self.quantity_digits),
'uom_symbol': self.quantity_uom.symbol,
'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'),
}
return recname
@ -127,10 +441,10 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
cashbook = Cashbook.browse([id_cashbook])[0]
if isinstance(values, dict):
type_ = values.get('bookingtype', None)
type_ = values.get('bookingtype', getattr(line, 'bookingtype', None))
quantity = values.get('quantity', None)
else :
type_ = getattr(values, 'bookingtype', None)
type_ = getattr(values, 'bookingtype', getattr(line, 'bookingtype', None))
quantity = getattr(values, 'quantity', None)
if (type_ is not None) and (cashbook.feature == 'asset'):
@ -147,6 +461,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
})
else :
raise ValueError('invalid "bookingtype"')
return result
@classmethod
@ -181,19 +496,16 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
'quantity_2nd_uom': splitline.quantity \
if (asset_books == 2) and (diff_uom == True) else None,
})
elif booktransf_uom is None:
# counterpart-cashbook has no uom -> no quantity
elif sum([1 if booktransf_uom is not None else 0,
1 if line_uom is not None else 0]) == 1:
# one of the related cashbooks only is asset-type
result.update({
'quantity': None,
'quantity': line.quantity,
'quantity_2nd_uom': None,
})
else :
if line_uom is None:
result.update({
'quantity': line.quantity,
'quantity_2nd_uom': None,
})
elif line_uom == booktransf_uom:
elif sum([1 if booktransf_uom is not None else 0,
1 if line_uom is not None else 0]) == 2:
if line_uom == booktransf_uom:
result.update({
'quantity': line.quantity,
'quantity_2nd_uom': None,
@ -215,6 +527,42 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
if cnt1 > 0:
self.quantity = quantity
@fields.depends('quantity', 'cashbook', '_parent_cashbook.current_rate', 'currency_digits')
def on_change_with_current_value(self, name=None):
""" get current value of line by current stock marked price
and quantity
"""
if self.cashbook:
if (self.quantity is not None) and \
(self.cashbook.current_rate is not None):
return (
self.quantity * self.cashbook.current_rate
).quantize(Decimal(str(1/10**self.currency_digits)))
@fields.depends('quantity', 'amount', 'cashbook', '_parent_cashbook.current_rate', 'currency_digits')
def on_change_with_diff_amount(self, name=None):
""" get delta between buy and current value
"""
if self.cashbook:
if (self.quantity is not None) and \
(self.amount is not None) and \
(self.cashbook.current_rate is not None):
return (
self.quantity * self.cashbook.current_rate - self.amount
).quantize(Decimal(str(1/10**self.currency_digits)))
@fields.depends('quantity', 'amount', 'cashbook', '_parent_cashbook.current_rate')
def on_change_with_diff_percent(self, name=None):
""" get performane percent
"""
if self.cashbook:
if (self.quantity is not None) and \
(self.amount is not None) and (self.amount != Decimal('0.0')) and \
(self.cashbook.current_rate is not None):
return (self.quantity * self.cashbook.current_rate * \
Decimal('100.0') / self.amount - Decimal('100.0')
).quantize(Decimal(str(1/10**self.currency_digits)))
@fields.depends('splitlines')
def on_change_with_splitline_has_quantity(self, name=None):
""" get True if splitlines are linked to asset-cashbooks
@ -258,20 +606,33 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
self.amount / self.quantity
).quantize(Decimal(Decimal(1) / 10**digit))
@fields.depends('cashbook', '_parent_cashbook.quantity_uom')
@fields.depends('feature', 'cashbook', '_parent_cashbook.quantity_uom', \
'booktransf', '_parent_booktransf.quantity_uom', '_parent_booktransf.feature')
def on_change_with_quantity_uom(self, name=None):
""" get quantity-unit of asset
"""
if self.cashbook:
if self.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
if self.feature == 'asset':
if self.cashbook:
if self.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
else :
if self.booktransf:
if self.booktransf.feature == 'asset':
if self.booktransf.quantity_uom:
return self.booktransf.quantity_uom.id
@fields.depends('cashbook', '_parent_cashbook.quantity_digits')
@fields.depends('feature', 'cashbook', '_parent_cashbook.quantity_digits', \
'booktransf', '_parent_booktransf.quantity_digits', '_parent_booktransf.feature')
def on_change_with_quantity_digits(self, name=None):
""" get digits from cashbook
"""
if self.cashbook:
return self.cashbook.quantity_digits
if self.feature == 'asset':
if self.cashbook:
return self.cashbook.quantity_digits
else :
if self.booktransf:
if self.booktransf.feature == 'asset':
return self.booktransf.quantity_digits
return 4
@classmethod
@ -295,13 +656,14 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
))
# quantity and amount must with same sign
(amount_sign, a_dig, a_exp) = line.amount.as_tuple()
(quantity_sign, q_dig, q_exp) = line.quantity.as_tuple()
if amount_sign != quantity_sign:
raise UserError(gettext(
'cashbook_investment.msg_line_sign_mismatch',
linetxt = line.rec_name,
))
if (line.amount != Decimal('0.0')) and (line.quantity != Decimal('0.0')):
(amount_sign, a_dig, a_exp) = line.amount.as_tuple()
(quantity_sign, q_dig, q_exp) = line.quantity.as_tuple()
if amount_sign != quantity_sign:
raise UserError(gettext(
'cashbook_investment.msg_line_sign_mismatch',
linetxt = line.rec_name,
))
@classmethod
def update_values_by_splitlines(cls, lines):

View file

@ -23,6 +23,22 @@ msgid "Cannot transfer quantities between cashbooks with different unit-categori
msgstr "Es können keine Mengen zwischen Kassenbüchern mit verschiedenen Einheitenkategorien (%(cat1)s != %(cat2)s) übertragen werden."
##############
# ir.ui.menu #
##############
msgctxt "model:ir.ui.menu,name:menu_assetconf"
msgid "Asset setting"
msgstr "Vermögenswert"
#############
# ir.action #
#############
msgctxt "model:ir.action,name:act_assetconf_form"
msgid "Asset setting"
msgstr "Vermögenswert-Einstellung"
#################
# cashbook.book #
#################
@ -34,6 +50,10 @@ msgctxt "view:cashbook.book:"
msgid "Quantity"
msgstr "Anzahl"
msgctxt "view:cashbook.book:"
msgid "Fees and dividends"
msgstr "Gebühren und Dividenden"
msgctxt "view:cashbook.book:"
msgid "Current valuation of the investment"
msgstr "aktuelle Bewertung der Vermögensanlage"
@ -122,6 +142,70 @@ msgctxt "help:cashbook.book,current_rate:"
msgid "Rate per unit of investment based on current stock exchange price."
msgstr "Kurs pro Einheit der Investition anhand des aktuellen Börsenkurses."
msgctxt "field:cashbook.book,yield_dividend_total:"
msgid "Dividend"
msgstr "Dividende"
msgctxt "help:cashbook.book,yield_dividend_total:"
msgid "Total dividends received"
msgstr "insgesamt erhaltene Dividenden"
msgctxt "field:cashbook.book,yield_dividend_12m:"
msgid "Dividend 1y"
msgstr "Dividende 1J"
msgctxt "help:cashbook.book,yield_dividend_12m:"
msgid "Dividends received in the last twelve months"
msgstr "in den letzten zwölf Monaten erhaltene Dividenden"
msgctxt "field:cashbook.book,yield_fee_total:"
msgid "Trade Fee"
msgstr "Handelsgebühr"
msgctxt "help:cashbook.book,yield_fee_total:"
msgid "Total trade fees payed"
msgstr "insgesamt bezahlte Handelsgebühr"
msgctxt "field:cashbook.book,yield_fee_12m:"
msgid "Trade Fee 1y"
msgstr "Handelsgebühr 1J"
msgctxt "help:cashbook.book,yield_fee_12m:"
msgid "Trade fees payed in the last twelve month"
msgstr "in den letzten zwölf Monaten bezahlte Handelsgebühr"
msgctxt "field:cashbook.book,yield_sales:"
msgid "Sales"
msgstr "Verkäufe"
msgctxt "help:cashbook.book,yield_sales:"
msgid "Total profit or loss on sale of shares."
msgstr "Gesamter Gewinn oder Verlust bei Verkauf von Anteilen."
msgctxt "field:cashbook.book,yield_sales_12m:"
msgid "Sales 1y"
msgstr "Verkäufe 1J"
msgctxt "help:cashbook.book,yield_sales_12m:"
msgid "Total profit or loss on sale of shares in the last twelve month."
msgstr "Gesamter Gewinn oder Verlust bei Verkauf von Anteilen in den letzten zwölf Monaten."
msgctxt "field:cashbook.book,yield_balance:"
msgid "Total Yield"
msgstr "Gesamtertrag"
msgctxt "help:cashbook.book,yield_balance:"
msgid "Total income: price gain + dividends + sales gains - fees"
msgstr "Gesamtertrag: Kursgewinn + Dividenden + Verkaufsgewinne - Gebühren"
msgctxt "field:cashbook.book,purchase_amount:"
msgid "Purchase Amount"
msgstr "Kaufbetrag"
msgctxt "help:cashbook.book,purchase_amount:"
msgid "Total purchase amount, from shares and fees."
msgstr "Kaufbetrag gesamt, aus Anteilen und Gebühren."
##################
# cashbook.split #
@ -162,6 +246,10 @@ msgstr "Umrechnungsfaktor zwischen den Einheiten der teilnehmenden Kassenbücher
#################
# cashbook.line #
#################
msgctxt "view:cashbook.line:"
msgid "Performance"
msgstr "Wertentwicklung"
msgctxt "field:cashbook.line,quantity_digits:"
msgid "Digits"
msgstr "Dezimalstellen"
@ -218,6 +306,54 @@ msgctxt "field:cashbook.line,splitline_has_quantity:"
msgid "has quantity"
msgstr "hat Anzahl"
msgctxt "field:cashbook.line,current_value:"
msgid "Value"
msgstr "Wert"
msgctxt "help:cashbook.line,current_value:"
msgid "Valuation of the investment based on the current stock market price."
msgstr "Bewertung der Vermögensanlage anhand des aktuellen Börsenkurses."
msgctxt "field:cashbook.line,diff_amount:"
msgid "Difference"
msgstr "Differenz"
msgctxt "help:cashbook.line,diff_amount:"
msgid "Difference between acquisition value and current value"
msgstr "Differenz zwischen Anschaffungswert und aktuellem Wert"
msgctxt "field:cashbook.line,diff_percent:"
msgid "Percent"
msgstr "Prozent"
msgctxt "help:cashbook.line,diff_percent:"
msgid "percentage performance since acquisition"
msgstr "prozentuale Wertentwicklung seit Anschaffung"
msgctxt "field:cashbook.line,trade_fee:"
msgid "Fee"
msgstr "Gebühr"
msgctxt "help:cashbook.line,trade_fee:"
msgid "Trading fee for the current booking line."
msgstr "Handelsgebühr für die aktuelle Buchungszeile."
msgctxt "field:cashbook.line,asset_dividend:"
msgid "Dividend"
msgstr "Dividende"
msgctxt "help:cashbook.line,asset_dividend:"
msgid "Dividend received at the current booking line."
msgstr "Erhaltene Dividende an der aktuellen Buchungszeile."
msgctxt "field:cashbook.line,asset_gainloss:"
msgid "Profit/Loss"
msgstr "Gewinn/Verlust"
msgctxt "help:cashbook.line,asset_gainloss:"
msgid "Profit or loss on sale on the current booking line."
msgstr "Gewinn oder Verlust bei Verkauf auf der aktuellen Buchungszeile."
##################
# cashbook.recon #
@ -237,3 +373,35 @@ msgstr "Anzahl-Dezimalstellen"
msgctxt "field:cashbook.recon,quantity_uom:"
msgid "Symbol"
msgstr "Symbol"
######################
# cashbook.assetconf #
######################
msgctxt "model:cashbook.assetconf,name:"
msgid "Asset setting"
msgstr "Vermögenswert-Einstellung"
msgctxt "field:cashbook.assetconf,fee_category:"
msgid "Fee category"
msgstr "Gebührenkategorie"
msgctxt "help:cashbook.assetconf,fee_category:"
msgid "Category for fees when trading assets."
msgstr "Kategorie für Gebühren beim Handel mit Vermögenswerten."
msgctxt "field:cashbook.assetconf,dividend_category:"
msgid "Dividend category"
msgstr "Dividendenkategorie"
msgctxt "help:cashbook.assetconf,dividend_category:"
msgid "Category for dividend paid out."
msgstr "Kategorie für ausgezahlte Dividenden."
msgctxt "field:cashbook.assetconf,gainloss_book:"
msgid "Profit/Loss Cashbook"
msgstr "Gewinn/Verlust Kassenbuch"
msgctxt "help:cashbook.assetconf,gainloss_book:"
msgid "Profit and loss on sale of assets are recorded in the cash book."
msgstr "Gewinn und Verlust bei Verkauf von Vermögenswerten werden auf das Kassenbuch gebucht."

View file

@ -18,6 +18,14 @@ msgctxt "model:ir.message,text:msg_uomcat_mismatch"
msgid "Cannot transfer quantities between cashbooks with different unit-categories (%(cat1)s != %(cat2)s)."
msgstr "Cannot transfer quantities between cashbooks with different unit-categories (%(cat1)s != %(cat2)s)."
msgctxt "model:ir.ui.menu,name:menu_assetconf"
msgid "Asset setting"
msgstr "Asset setting"
msgctxt "model:ir.action,name:act_assetconf_form"
msgid "Asset setting"
msgstr "Asset setting"
msgctxt "view:cashbook.book:"
msgid "Asset"
msgstr "Asset"
@ -26,6 +34,10 @@ msgctxt "view:cashbook.book:"
msgid "Quantity"
msgstr "Quantity"
msgctxt "view:cashbook.book:"
msgid "Fees and dividends"
msgstr "Fees and dividends"
msgctxt "view:cashbook.book:"
msgid "Current valuation of the investment"
msgstr "Current valuation of the investment"
@ -114,6 +126,70 @@ msgctxt "help:cashbook.book,current_rate:"
msgid "Rate per unit of investment based on current stock exchange price."
msgstr "Rate per unit of investment based on current stock exchange price."
msgctxt "field:cashbook.book,yield_dividend_total:"
msgid "Dividend"
msgstr "Dividend"
msgctxt "help:cashbook.book,yield_dividend_total:"
msgid "Total dividends received"
msgstr "Total dividends received"
msgctxt "field:cashbook.book,yield_dividend_12m:"
msgid "Dividend 1y"
msgstr "Dividend 1y"
msgctxt "help:cashbook.book,yield_dividend_12m:"
msgid "Dividends received in the last twelve months"
msgstr "Dividends received in the last twelve months"
msgctxt "field:cashbook.book,yield_fee_total:"
msgid "Trade Fee"
msgstr "Trade Fee"
msgctxt "help:cashbook.book,yield_fee_total:"
msgid "Total trade fees payed"
msgstr "Total trade fees payed"
msgctxt "field:cashbook.book,yield_fee_12m:"
msgid "Trade Fee 1y"
msgstr "Trade Fee 1y"
msgctxt "help:cashbook.book,yield_fee_12m:"
msgid "Trade fees payed in the last twelve month"
msgstr "Trade fees payed in the last twelve month"
msgctxt "field:cashbook.book,yield_sales:"
msgid "Sales"
msgstr "Sales"
msgctxt "help:cashbook.book,yield_sales:"
msgid "Total profit or loss on sale of shares."
msgstr "Total profit or loss on sale of shares."
msgctxt "field:cashbook.book,yield_sales_12m:"
msgid "Sales 1y"
msgstr "Sales 1y"
msgctxt "help:cashbook.book,yield_sales_12m:"
msgid "Total profit or loss on sale of shares in the last twelve month."
msgstr "Total profit or loss on sale of shares in the last twelve month."
msgctxt "field:cashbook.book,yield_balance:"
msgid "Total Yield"
msgstr "Total Yield"
msgctxt "help:cashbook.book,yield_balance:"
msgid "Total income: price gain + dividends + sales gains - fees"
msgstr "Total income: price gain + dividends + sales gains - fees"
msgctxt "field:cashbook.book,purchase_amount:"
msgid "Purchase Amount"
msgstr "Purchase Amount"
msgctxt "help:cashbook.book,purchase_amount:"
msgid "Total purchase amount, from shares and fees."
msgstr "Total purchase amount, from shares and fees."
msgctxt "field:cashbook.split,quantity_digits:"
msgid "Digits"
msgstr "Digits"
@ -146,6 +222,10 @@ msgctxt "help:cashbook.split,factor_2nd_uom:"
msgid "Conversion factor between the units of the participating cash books."
msgstr "Conversion factor between the units of the participating cash books."
msgctxt "view:cashbook.line:"
msgid "Performance"
msgstr "Performance"
msgctxt "field:cashbook.line,quantity_digits:"
msgid "Digits"
msgstr "Digits"
@ -202,6 +282,54 @@ msgctxt "field:cashbook.line,splitline_has_quantity:"
msgid "has quantity"
msgstr "has quantity"
msgctxt "field:cashbook.line,current_value:"
msgid "Value"
msgstr "Value"
msgctxt "help:cashbook.line,current_value:"
msgid "Valuation of the investment based on the current stock market price."
msgstr "Valuation of the investment based on the current stock market price."
msgctxt "field:cashbook.line,diff_amount:"
msgid "Difference"
msgstr "Difference"
msgctxt "help:cashbook.line,diff_amount:"
msgid "Difference between acquisition value and current value"
msgstr "Difference between acquisition value and current value"
msgctxt "field:cashbook.line,diff_percent:"
msgid "Percent"
msgstr "Percent"
msgctxt "help:cashbook.line,diff_percent:"
msgid "percentage performance since acquisition"
msgstr "percentage performance since acquisition"
msgctxt "field:cashbook.line,trade_fee:"
msgid "Fee"
msgstr "Fee"
msgctxt "help:cashbook.line,trade_fee:"
msgid "Trading fee for the current booking line."
msgstr "Trading fee for the current booking line."
msgctxt "field:cashbook.line,asset_dividend:"
msgid "Dividend"
msgstr "Dividend"
msgctxt "help:cashbook.line,asset_dividend:"
msgid "Dividend received at the current booking line."
msgstr "Dividend received at the current booking line."
msgctxt "field:cashbook.line,asset_gainloss:"
msgid "Profit/Loss"
msgstr "Profit/Loss"
msgctxt "help:cashbook.line,asset_gainloss:"
msgid "Profit or loss on sale on the current booking line."
msgstr "Profit or loss on sale on the current booking line."
msgctxt "field:cashbook.recon,start_quantity:"
msgid "Start Quantity"
msgstr "Start Quantity"
@ -214,3 +342,31 @@ msgctxt "field:cashbook.recon,quantity_digits:"
msgid "Quantity Digits"
msgstr "Quantity Digits"
msgctxt "field:cashbook.recon,quantity_uom:"
msgid "Symbol"
msgstr "Symbol"
msgctxt "model:cashbook.assetconf,name:"
msgid "Asset setting"
msgstr "Asset setting"
msgctxt "field:cashbook.assetconf,fee_category:"
msgid "Fee category"
msgstr "Fee category"
msgctxt "help:cashbook.assetconf,fee_category:"
msgid "Category for fees when trading assets."
msgstr "Category for fees when trading assets."
msgctxt "field:cashbook.assetconf,dividend_category:"
msgid "Dividend category"
msgstr "Dividend category"
msgctxt "help:cashbook.assetconf,dividend_category:"
msgid "Category for dividend paid out."
msgstr "Category for dividend paid out."
msgctxt "field:cashbook.assetconf,gainloss_book:"
msgid "Profit/Loss Cashbook"
msgstr "Profit/Loss Cashbook"

18
menu.xml Normal file
View file

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<!-- This file is part of the cashbook-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<tryton>
<data>
<!-- menu: /Cashbook/Configuration/Asset seetting -->
<menuitem id="menu_assetconf" action="act_assetconf_form"
icon="tryton-settings"
parent="cashbook.menu_config" sequence="30"/>
<record model="ir.ui.menu-res.group" id="menu_assetconf-group_cashbook_admin">
<field name="menu" ref="menu_assetconf"/>
<field name="group" ref="cashbook.group_cashbook_admin"/>
</record>
</data>
</tryton>

View file

@ -42,7 +42,7 @@ class Reconciliation(metaclass=PoolMeta):
digits=self.quantity_digits),
'end_quantity': Report.format_number(self.end_quantity or 0.0, None,
digits=self.quantity_digits),
'uom_symbol': self.quantity_uom.symbol,
'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'),
}
return recname
@ -59,7 +59,7 @@ class Reconciliation(metaclass=PoolMeta):
""" quantity_digits of cashbook
"""
if self.cashbook:
return self.cashbook.currency.digits
return self.cashbook.quantity_digits
else:
return 4

View file

@ -99,7 +99,7 @@ setup(name='%s_%s' % (PREFIX, MODULE),
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
'view/*.xml',
'view/*.xml', 'docs/*.txt',
'versiondep.txt', 'README.rst']),
},

View file

@ -47,23 +47,32 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta):
}
return recname
@fields.depends('line', '_parent_line.cashbook', 'booktransf', '_parent_booktransf.quantity_uom')
@fields.depends('line', '_parent_line.cashbook', 'booktransf', \
'_parent_booktransf.feature', '_parent_booktransf.quantity_uom')
def on_change_with_quantity_uom(self, name=None):
""" get quantity-unit of asset
"""
if self.line:
if self.line.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
if self.line.cashbook.feature == 'asset':
if self.line.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
if self.booktransf:
if self.booktransf.quantity_uom:
return self.booktransf.quantity_uom.id
if self.booktransf.feature == 'asset':
if self.booktransf.quantity_uom:
return self.booktransf.quantity_uom.id
@fields.depends('line', '_parent_line.cashbook')
@fields.depends('line', '_parent_line.cashbook', 'booktransf', \
'_parent_booktransf.feature', '_parent_booktransf.quantity_digits')
def on_change_with_quantity_digits(self, name=None):
""" get digits from cashbook
"""
if self.line:
return self.line.cashbook.quantity_digits
if self.line.cashbook.feature == 'asset':
return self.line.cashbook.quantity_digits
if self.booktransf:
if self.booktransf.feature == 'asset':
return self.booktransf.quantity_digits
return 4
@classmethod

View file

@ -6,6 +6,7 @@ import unittest
from trytond.modules.cashbook_investment.tests.test_book import CbInvTestCase
from trytond.modules.cashbook_investment.tests.test_reconciliation import ReconTestCase
from trytond.modules.cashbook_investment.tests.test_yield import YieldTestCase
__all__ = ['suite']
@ -14,6 +15,7 @@ __all__ = ['suite']
class CashbookInvestmentTestCase(\
CbInvTestCase,\
ReconTestCase,\
YieldTestCase,\
):
'Test cashbook-investment module'
module = 'cashbook_investment'

View file

@ -48,6 +48,168 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
self.assertEqual(book.state_string, 'Open')
self.assertEqual(book.feature, 'asset')
self.assertEqual(book.quantity_digits, 4)
self.assertEqual(book.show_performance, True)
@with_transaction()
def test_assetbook_aggregated_values(self):
""" create cashbooks with hierarchy, add lines,
check values at non-type-books
"""
pool = Pool()
Book = pool.get('cashbook.book')
BType = pool.get('cashbook.type')
Asset = pool.get('investment.asset')
company = self.prep_company()
type_depot = self.prep_type('Depot', 'D')
type_cash = self.prep_type('Cash', 'C')
category_in = self.prep_category(cattype='in')
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 1),
'rate': Decimal('10.0'),
}, {
'date': date(2022, 5, 2),
'rate': Decimal('12.5'),
}])],
}])
self.assertEqual(asset.rec_name, 'Product 1 | 12.5000 usd/u | 05/02/2022')
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
with Transaction().set_context({
'company': company.id,
}):
books = Book.create([{
'name': 'L0-Euro-None',
'btype': None,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'childs': [('create', [{
'name': 'L1-Euro-Cash',
'btype': type_cash.id,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Cat In',
'category': category_in.id,
'bookingtype': 'in',
'amount': Decimal('15.0'),
}])],
}, {
'name': 'L1-USD-Cash',
'btype': type_cash.id,
'company': company.id,
'currency': usd.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Cat In',
'category': category_in.id,
'bookingtype': 'in',
'amount': Decimal('15.0'), # 14.29 €
}])],
}, {
'name': 'L1-Euro-Depot',
'btype': type_depot.id,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'asset': asset.id,
'quantity_uom': asset.uom.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Cat In',
'category': category_in.id,
'bookingtype': 'in',
'amount': Decimal('15.0'),
'quantity': Decimal('1.0'),
}])],
}, {
'name': 'L1-USD-Depot',
'btype': type_depot.id,
'company': company.id,
'currency': usd.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
'asset': asset.id,
'quantity_uom': asset.uom.id,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'Cat In',
'category': category_in.id,
'bookingtype': 'in',
'amount': Decimal('15.0'), # 14.29 €
'quantity': Decimal('1.0'),
}])],
}])],
}])
self.assertEqual(len(books), 1)
self.assertEqual(books[0].rec_name, 'L0-Euro-None')
self.assertEqual(books[0].balance, Decimal('58.57'))
self.assertEqual(books[0].balance_ref, Decimal('58.57'))
# balance of asset-books: 29,286 €
# value of asset-books: 11.9€ + 12.5USD/1.05 = 23.8€
self.assertEqual(books[0].current_value, Decimal('23.8'))
self.assertEqual(books[0].current_value_ref, Decimal('23.8'))
self.assertEqual(books[0].diff_amount, Decimal('-5.49'))
self.assertEqual(books[0].diff_percent, Decimal('-18.74'))
self.assertEqual(len(books[0].childs), 4)
self.assertEqual(books[0].childs[0].rec_name,
'L0-Euro-None/L1-Euro-Cash | 15.00 € | Open')
self.assertEqual(books[0].childs[0].current_value, None)
self.assertEqual(books[0].childs[0].current_value_ref, None)
self.assertEqual(books[0].childs[0].diff_amount, None)
self.assertEqual(books[0].childs[0].diff_percent, None)
self.assertEqual(books[0].childs[1].rec_name,
'L0-Euro-None/L1-Euro-Depot | 15.00 € | Open | 1.0000 u')
self.assertEqual(books[0].childs[1].asset.rec_name,
'Product 1 | 12.5000 usd/u | 05/02/2022')
self.assertEqual(books[0].childs[1].current_value, Decimal('11.9'))
self.assertEqual(books[0].childs[1].current_value_ref, Decimal('11.9'))
self.assertEqual(books[0].childs[1].diff_amount, Decimal('-3.1'))
self.assertEqual(books[0].childs[1].diff_percent, Decimal('-20.67'))
self.assertEqual(books[0].childs[2].rec_name,
'L0-Euro-None/L1-USD-Cash | 15.00 usd | Open')
self.assertEqual(books[0].childs[2].current_value, None)
self.assertEqual(books[0].childs[2].current_value_ref, None)
self.assertEqual(books[0].childs[2].diff_amount, None)
self.assertEqual(books[0].childs[2].diff_percent, None)
self.assertEqual(books[0].childs[3].rec_name,
'L0-Euro-None/L1-USD-Depot | 15.00 usd | Open | 1.0000 u')
self.assertEqual(books[0].childs[3].asset.rec_name,
'Product 1 | 12.5000 usd/u | 05/02/2022')
self.assertEqual(books[0].childs[3].current_value, Decimal('12.5'))
self.assertEqual(books[0].childs[3].current_value_ref, Decimal('11.9'))
self.assertEqual(books[0].childs[3].diff_amount, Decimal('-2.5'))
self.assertEqual(books[0].childs[3].diff_percent, Decimal('-16.67'))
@with_transaction()
def test_assetbook_create_line(self):
@ -134,6 +296,9 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0'))
self.assertEqual(book.lines[0].quantity_digits, 3)
self.assertEqual(book.lines[0].quantity_uom.symbol, 'u')
self.assertEqual(book.lines[0].current_value, Decimal('3.88'))
self.assertEqual(book.lines[0].diff_amount, Decimal('1.38'))
self.assertEqual(book.lines[0].diff_percent, Decimal('55.18'))
self.assertEqual(book.lines[1].amount, Decimal('4.0'))
self.assertEqual(book.lines[1].quantity, Decimal('3.3'))
@ -267,7 +432,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
self.assertEqual(book2.quantity_all, Decimal('20.0'))
# usd --> eur: 1750 US$ / 1.05 = 1666.666 €
# 1 ounce --> 20 gram: 1666.666 € * 20 / 28.3495 = 1175.7996 €
# bette we use 'Troy Ounce': 1 oz.tr. = 31.1034768 gram
# better we use 'Troy Ounce': 1 oz.tr. = 31.1034768 gram
self.assertEqual(book2.current_value, Decimal('1175.80'))
self.assertEqual(book2.current_value_ref, Decimal('1175.80'))
self.assertEqual(book2.diff_amount, Decimal('-74.20'))
@ -420,6 +585,96 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
book.on_change_asset()
self.assertEqual(book.quantity_uom.rec_name, 'Unit')
@with_transaction()
def test_assetbook_quantity_digits(self):
""" check selection of quantity-digits
"""
pool = Pool()
Book = pool.get('cashbook.book')
BType = pool.get('cashbook.type')
company = self.prep_company()
category = self.prep_category(cattype='in')
type_depot = self.prep_type('Depot', 'D')
type_cash = self.prep_type('Cash', 'C')
BType.write(*[
[type_depot],
{
'feature': 'asset',
'name': 'Asset',
'short': 'A',
}])
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
books = Book.create([{
'name': 'Book 1',
'btype': type_depot.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'quantity_digits': 3,
'start_date': date(2022, 5, 1),
}, {
'name': 'Book 2',
'btype': type_cash.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])
self.assertEqual(books[0].rec_name, 'Book 1 | 0.00 usd | Open | 0.000 u')
self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open')
Book.write(*[
[books[0]],
{
'lines': [('create', [{
'bookingtype': 'in',
'date': date(2022, 5, 1),
'category': category.id,
'amount': Decimal('1.0'),
'quantity': Decimal('1.0'),
}])],
}])
self.assertEqual(books[0].lines[0].rec_name,
'05/01/2022|Rev|1.00 usd|- [Cat1]|1.000 u')
self.assertEqual(books[0].lines[0].quantity_digits, 3)
Book.write(*[
[books[1]],
{
'lines': [('create', [{
'bookingtype': 'in',
'date': date(2022, 5, 1),
'category': category.id,
'amount': Decimal('1.0'),
}])],
}])
self.assertEqual(books[1].lines[0].rec_name,
'05/01/2022|Rev|1.00 usd|- [Cat1]')
self.assertEqual(books[1].lines[0].quantity_digits, 4)
Book.write(*[
[books[1]],
{
'lines': [('create', [{
'bookingtype': 'mvin',
'date': date(2022, 5, 1),
'amount': Decimal('1.0'),
'quantity': Decimal('1.0'),
'booktransf': books[0].id,
}])],
}])
self.assertEqual(books[1].lines[1].rec_name,
'05/01/2022|from|1.00 usd|- [Book 1 | 1.00 usd | Open | 1.000 u]')
self.assertEqual(books[1].lines[1].quantity_digits, 3)
@with_transaction()
def test_assetbook_book_with_asset(self):
""" create cashbook, set 'btype' to asset
@ -576,6 +831,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])
self.assertEqual(book2.show_performance, True)
book, = Book.create([{
'name': 'Book 1',
@ -594,6 +850,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
'quantity': Decimal('1.5'),
}])],
}])
self.assertEqual(book.show_performance, False)
self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open')
self.assertEqual(len(book.lines), 1)
self.assertEqual(book.lines[0].quantity, Decimal('1.5'))
@ -677,6 +934,87 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
book.lines = l1
book.lines[-1].on_change_quantity()
@with_transaction()
def test_assetbook_check_mvout_zero_quantity(self):
""" create cashbook + line, bookingtype 'mvout'
transfer from asset-book to cash-book - zero quantity,
to book gain/loss
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
Category = pool.get('cashbook.category')
BType = pool.get('cashbook.type')
type_cash = self.prep_type()
type_depot = self.prep_type('Depot', 'D')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
category_out = self.prep_category(name='Out Category', cattype='out')
company = self.prep_company()
party = self.prep_party()
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
book1, = Book.create([{
'name': 'Asset-Book',
'btype': type_depot.id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])
book2, = Book.create([{
'name': 'Cash-Book',
'btype': type_cash.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])
Book.write(*[
[book1],
{
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'loss at sell',
'category': category_out.id,
'bookingtype': 'mvout',
'amount': Decimal('10.0'),
'booktransf': book2.id,
'quantity': Decimal('0.0'),
}])],
},
])
self.assertEqual(book1.rec_name, 'Asset-Book | -10.00 usd | Open | 0.0000 u')
self.assertEqual(len(book1.lines), 1)
self.assertEqual(book1.lines[0].rec_name,
'05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | 0.00 usd | Open]|0.0000 u')
self.assertEqual(book2.rec_name, 'Cash-Book | 0.00 usd | Open')
self.assertEqual(len(book2.lines), 0)
Line.wfcheck(list(book1.lines))
self.assertEqual(book1.rec_name, 'Asset-Book | -10.00 usd | Open | 0.0000 u')
self.assertEqual(len(book1.lines), 1)
self.assertEqual(book1.lines[0].rec_name,
'05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | 10.00 usd | Open]|0.0000 u')
self.assertEqual(book2.rec_name, 'Cash-Book | 10.00 usd | Open')
self.assertEqual(len(book2.lines), 1)
self.assertEqual(book2.lines[0].rec_name,
'05/01/2022|from|10.00 usd|loss at sell [Asset-Book | -10.00 usd | Open | 0.0000 u]')
@with_transaction()
def test_assetbook_check_mvin(self):
""" create cashbook + line, bookingtype 'mvin'
@ -1401,6 +1739,129 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
self.assertEqual(books[1].lines[0].rec_name,
'05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open | 4.00 u]|-2.50 u')
@with_transaction()
def test_assetbook_split_in_category_and_assetbook_zero_quantity(self):
""" splitbooking incoming to asset-cahbook,
from category and asset-cashbook, zero qunatity to book
gain/loss of a sell from asset-cashbook
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
BType = pool.get('cashbook.type')
Asset = pool.get('investment.asset')
ProdTempl = pool.get('product.template')
Uom = pool.get('product.uom')
types = self.prep_type()
BType.write(*[
[types],
{
'feature': 'asset',
}])
category = self.prep_category(cattype='in')
company = self.prep_company()
party = self.prep_party()
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
books = Book.create([{
'start_date': date(2022, 4, 1),
'name': 'Book 1',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'quantity_digits': 2,
}, {
'start_date': date(2022, 4, 1),
'name': 'Book 2',
'btype': types.id,
'company': company.id,
'currency': company.currency.id,
'number_sequ': self.prep_sequence().id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'quantity_digits': 2,
}])
Book.write(*[
[books[0]],
{
'lines': [('create', [{
'bookingtype': 'spin',
'date': date(2022, 5, 1),
'splitlines': [('create', [{
'amount': Decimal('5.0'),
'splittype': 'cat',
'description': 'gain on sell',
'category': category.id,
'quantity': Decimal('0.0'),
}, {
'amount': Decimal('6.0'),
'splittype': 'tr',
'description': 'transfer zero quantity',
'booktransf': books[1].id,
'quantity': Decimal('0.0'),
}])],
}])],
}])
self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 0.00 u')
self.assertEqual(books[0].balance_all, Decimal('11.0'))
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u')
self.assertEqual(books[0].lines[0].splitline_has_quantity, True)
self.assertEqual(len(books[0].lines[0].splitlines), 2)
self.assertEqual(books[0].lines[0].splitlines[0].rec_name,
'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u')
self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None)
self.assertEqual(books[0].lines[0].splitlines[1].rec_name,
'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | 0.00 usd | Open | 0.00 u]|0.00 u')
self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name,
'Book 2 | 0.00 usd | Open | 0.00 u')
self.assertEqual(len(books[0].lines[0].references), 0)
self.assertEqual(books[0].lines[0].reference, None)
self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u')
self.assertEqual(books[1].balance_all, Decimal('0.0'))
self.assertEqual(len(books[1].lines), 0)
Line.wfcheck([books[0].lines[0]])
self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 0.00 u')
self.assertEqual(books[0].balance_all, Decimal('11.0'))
self.assertEqual(len(books[0].lines), 1)
self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u')
self.assertEqual(books[0].lines[0].splitline_has_quantity, True)
self.assertEqual(len(books[0].lines[0].splitlines), 2)
self.assertEqual(books[0].lines[0].splitlines[0].rec_name,
'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u')
self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None)
self.assertEqual(books[0].lines[0].splitlines[1].rec_name,
'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | -6.00 usd | Open | 0.00 u]|0.00 u')
self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name,
'Book 2 | -6.00 usd | Open | 0.00 u')
self.assertEqual(len(books[0].lines[0].references), 1)
self.assertEqual(books[0].lines[0].references[0].rec_name,
'05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | 11.00 usd | Open | 0.00 u]|0.00 u')
self.assertEqual(books[0].lines[0].reference, None)
self.assertEqual(books[1].rec_name, 'Book 2 | -6.00 usd | Open | 0.00 u')
self.assertEqual(books[1].balance_all, Decimal('-6.0'))
self.assertEqual(len(books[1].lines), 1)
self.assertEqual(books[1].lines[0].rec_name,
'05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | 11.00 usd | Open | 0.00 u]|0.00 u')
@with_transaction()
def test_assetbook_split_in_catergory_asset_diff_unit(self):
""" splitbooking incoming to asset-cahbook,
@ -1744,7 +2205,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
@with_transaction()
def test_assetbook_split_out_category_and_assetbook(self):
""" splitbooking outgoing,
from asset-cashbook to asset-cahbook and to category
from asset-cashbook to asset-cashbook and to category
"""
pool = Pool()
Book = pool.get('cashbook.book')

View file

@ -54,11 +54,11 @@ class ReconTestCase(ModuleTestCase):
self.assertEqual(book.name, 'Asset-Book')
self.assertEqual(book.reconciliations[0].feature, 'asset')
self.assertEqual(book.reconciliations[0].rec_name,
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u')
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.0000 u - 0.0000 u')
Reconciliation.wfcheck(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].rec_name,
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u')
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.0000 u - 0.0000 u')
@with_transaction()
def test_recon_set_start_quantity_by_predecessor(self):
@ -91,6 +91,7 @@ class ReconTestCase(ModuleTestCase):
'currency': company.currency.id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'quantity_digits': 3,
'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{
@ -119,7 +120,7 @@ class ReconTestCase(ModuleTestCase):
self.assertEqual(book.name, 'Asset-Book')
self.assertEqual(len(book.reconciliations), 1)
self.assertEqual(book.reconciliations[0].rec_name,
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u')
'05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.000 u - 0.000 u')
self.assertEqual(len(book.reconciliations[0].lines), 0)
Lines.wfcheck(list(book.lines))
@ -134,7 +135,7 @@ class ReconTestCase(ModuleTestCase):
self.assertEqual(book.reconciliations[0].state, 'check')
self.assertEqual(book.reconciliations[0].rec_name,
'05/01/2022 - 05/31/2022 | 0.00 usd - 12.00 usd [2] | 0.00 u - 4.00 u')
'05/01/2022 - 05/31/2022 | 0.00 usd - 12.00 usd [2] | 0.000 u - 4.000 u')
Reconciliation.wfdone(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].state, 'done')
@ -144,9 +145,9 @@ class ReconTestCase(ModuleTestCase):
'date_to': date(2022, 6, 30),
}])
self.assertEqual(recons[0].rec_name,
'05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u')
'05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0] | 0.000 u - 0.000 u')
Reconciliation.wfcheck(recons)
self.assertEqual(recons[0].rec_name,
'05/31/2022 - 06/30/2022 | 12.00 usd - 12.00 usd [0] | 4.00 u - 4.00 u')
'05/31/2022 - 06/30/2022 | 12.00 usd - 12.00 usd [0] | 4.000 u - 4.000 u')
# end ReconTestCase

1338
tests/test_yield.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[tryton]
version=6.0.0
version=6.0.7
depends:
cashbook
investment
@ -9,3 +9,5 @@ xml:
line.xml
reconciliation.xml
splitline.xml
assetsetting.xml
menu.xml

View file

@ -1 +1,2 @@
cashbook;6.0.21;6.0.999;mds
cashbook;6.0.27;6.0.999;mds
investment;6.0.23;6.0.999;mds

14
view/assetconf_form.xml Normal file
View file

@ -0,0 +1,14 @@
<?xml version="1.0"?>
<!-- This file is part of the cashbook-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>
<label name="fee_category" />
<field name="fee_category"/>
<label name="dividend_category" />
<field name="dividend_category"/>
<label name="gainloss_book" />
<field name="gainloss_book"/>
</form>

View file

@ -4,29 +4,58 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<data>
<xpath expr="/form/notebook/page[@name='balance']/field[@name='balance_ref']"
position="after">
<separator colspan="4" name="quantity" string="Quantity"/>
<label name="quantity"/>
<field name="quantity" symbol="quantity_uom"/>
<label name="quantity_all"/>
<field name="quantity_all" symbol="quantity_uom"/>
<xpath expr="/form/notebook/page[@name='balance']/separator[@id='balance']" position="after">
<separator colspan="4" name="current_value" string="Current valuation of the investment"/>
</xpath>
<xpath expr="/form/notebook/page[@name='balance']/field[@name='balance']" position="after">
<label name="purchase_amount"/>
<field name="purchase_amount" symbol="currency"/>
<label name="yield_balance"/>
<field name="yield_balance" symbol="currency"/>
</xpath>
<xpath expr="/form/notebook/page[@name='balance']/field[@name='balance_all']" position="after">
<label name="current_value"/>
<field name="current_value" symbol="currency"/>
<label name="current_value_ref"/>
<field name="current_value_ref" symbol="company_currency"/>
<label name="diff_amount"/>
<field name="diff_amount" symbol="currency"/>
</xpath>
<xpath expr="/form/notebook/page[@name='balance']/field[@name='balance_ref']" position="after">
<label name="current_rate"/>
<field name="current_rate" symbol="asset_symbol"/>
<label name="diff_percent"/>
<group id="diff_percent" col="2">
<field name="diff_percent" xexpand="0"/>
<label name="diff_percent" xalign="0.0" string="%" xexpand="1"/>
</group>
<label name="current_rate"/>
<field name="current_rate" symbol="asset_symbol"/>
<label name="current_value_ref" colspan="2" string=" "/>
<label name="current_value_ref"/>
<field name="current_value_ref" symbol="company_currency"/>
<newline/>
<separator colspan="2" name="quantity" string="Quantity"/>
<newline/>
<label name="quantity"/>
<field name="quantity" symbol="quantity_uom"/>
<newline/>
<label name="quantity_all"/>
<field name="quantity_all" symbol="quantity_uom"/>
<newline/>
<separator name="yield_dividend_total" colspan="6" string="Fees and dividends"/>
<label name="yield_sales"/>
<field name="yield_sales" symbol="currency"/>
<label name="yield_dividend_total"/>
<field name="yield_dividend_total" symbol="currency"/>
<label name="yield_fee_total"/>
<field name="yield_fee_total" symbol="currency"/>
<label name="yield_sales_12m"/>
<field name="yield_sales_12m" symbol="currency"/>
<label name="yield_dividend_12m"/>
<field name="yield_dividend_12m" symbol="currency"/>
<label name="yield_fee_12m"/>
<field name="yield_fee_12m" symbol="currency"/>
</xpath>
<xpath expr="/form/notebook/page[@id='pggeneral']" position="after">

View file

@ -23,4 +23,29 @@ full copyright notices and license terms. -->
<field name="factor_2nd_uom"/>
</xpath>
<xpath expr="/form/notebook/page[@name='description']" position="after">
<page name="current_value" col="6" string="Performance">
<label name="current_value"/>
<field name="current_value" symbol="currency"/>
<label name="diff_amount"/>
<field name="diff_amount" symbol="currency"/>
<group id="diff_percent" col="2">
<field name="diff_percent" xexpand="0"/>
<label name="diff_percent" xalign="0.0" string="%" xexpand="1"/>
</group>
<newline/>
<label name="trade_fee"/>
<field name="trade_fee"/>
<label name="asset_dividend"/>
<field name="asset_dividend"/>
<newline/>
<label name="asset_gainloss"/>
<field name="asset_gainloss"/>
</page>
</xpath>
</data>