Compare commits

...

60 commits

Author SHA1 Message Date
Frederik Jaeckel
186b64e4b9 Version 6.0.11 2023-12-06 21:58:58 +01:00
Frederik Jaeckel
f3f0508821 tests: formatting 2023-12-03 17:37:43 +01:00
Frederik Jaeckel
cce59eb8bf formatting 2023-12-03 17:31:42 +01:00
Frederik Jaeckel
a7dda40f43 Etikett ver 6.0.10 zum Änderungssatz 2c7377b88a5a hinzugefügt 2023-06-08 17:18:58 +02:00
Frederik Jaeckel
0cc4ce05f1 Version 6.0.10 2023-06-08 17:18:43 +02:00
Frederik Jaeckel
3793efc161 tests: add company to context 2023-06-08 16:00:36 +02:00
Frederik Jaeckel
6495e0317e tests: formatting 2023-06-08 14:34:56 +02:00
Frederik Jaeckel
b84d7ea0fb formatting 2023-06-08 14:00:59 +02:00
Frederik Jaeckel
177dec2d7d Etikett ver 6.0.9 zum Änderungssatz 783aaee66f11 hinzugefügt 2023-04-19 09:45:04 +02:00
Frederik Jaeckel
4620f5aad2 Version 6.0.9 2023-04-19 09:44:56 +02:00
Frederik Jaeckel
7c67fb6de7 line: formatting, fix - on_change_with_quantity_digits() 2023-04-19 09:43:29 +02:00
Frederik Jaeckel
38e9477286 Etikett ver 6.0.8 zum Änderungssatz db9ce46e07e5 hinzugefügt 2023-04-17 12:03:36 +02:00
Frederik Jaeckel
dcb746a56b Version 6.0.8 2023-04-17 12:03:26 +02:00
Frederik Jaeckel
0332dd807e line: selection of digits for quantity 2023-04-17 12:01:51 +02:00
Frederik Jaeckel
a9b8f5552c Etikett ver 6.0.7 zum Änderungssatz 2f73b961caf1 hinzugefügt 2023-03-05 10:30:38 +01:00
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
27 changed files with 6545 additions and 2794 deletions

View file

@ -11,9 +11,66 @@ 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.11 - 06.12.2023*
- code optimized
*6.0.10 - 08.06.2023*
- code optimized
*6.0.9 - 19.04.2023*
- fix: line - exception if add split-lines
*6.0.8 - 17.04.2023*
- fix: line - selection of digits for quantity
*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')

47
asset.py Normal file
View file

@ -0,0 +1,47 @@
# -*- 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

37
assetsetting.py Normal file
View file

@ -0,0 +1,37 @@
# -*- 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>

659
book.py
View file

@ -9,121 +9,190 @@ 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',
'invisible': Eval('feature', '') != 'asset',
'readonly': Or(
STATES2['readonly'],
Len(Eval('lines')) > 0,
),
}, depends=DEPENDS2+['feature', 'lines'])
quantity_digits = fields.Integer(string='Digits',
help='Quantity Digits',
Len(Eval('lines')) > 0)},
depends=DEPENDS2+['feature', 'lines'])
quantity_digits = fields.Integer(
string='Digits', help='Quantity Digits',
domain=[
('quantity_digits', '>=', 0),
('quantity_digits', '<=', 6),
],
('quantity_digits', '<=', 6)],
states={
'required': Eval('feature', '') == 'asset',
'invisible': Eval('feature', '') != 'asset',
'readonly': Or(
STATES2['readonly'],
Len(Eval('lines')) > 0,
),
}, depends=DEPENDS2+['feature', 'lines'])
asset_uomcat = fields.Function(fields.Many2One(string='UOM Category',
readonly=True, model_name='product.uom.category',
Len(Eval('lines')) > 0)},
depends=DEPENDS2+['feature', 'lines'])
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)),
],
domain=[('category.id', '=', Eval('asset_uomcat', -1))],
states={
'required': Eval('feature', '') == 'asset',
'invisible': Eval('feature', '') != 'asset',
'readonly': Or(
STATES2['readonly'],
Len(Eval('lines')) > 0,
),
}, depends=DEPENDS2+['feature', 'lines', 'asset_uomcat'])
symbol = fields.Function(fields.Char(string='Symbol', readonly=True),
'on_change_with_symbol')
asset_symbol = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='cashbook.book'),
Len(Eval('lines')) > 0)},
depends=DEPENDS2+['feature', 'lines', 'asset_uomcat'])
symbol = fields.Function(fields.Char(
string='Symbol', readonly=True), 'on_change_with_symbol')
asset_symbol = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='cashbook.book'),
'on_change_with_asset_symbol')
quantity = fields.Function(fields.Numeric(string='Quantity',
help='Quantity of assets until to date', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature']),
'get_asset_quantity')
quantity_all = fields.Function(fields.Numeric(string='Total Quantity',
help='Total quantity of all assets', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature']),
'get_asset_quantity')
current_value = fields.Function(fields.Numeric(string='Value',
help='Valuation of the investment based on the current stock market price.',
quantity = fields.Function(fields.Numeric(
string='Quantity', help='Quantity of assets until to date',
readonly=True, digits=(16, Eval('quantity_digits', 4)),
states={'invisible': Eval('feature', '') != 'asset'},
depends=['quantity_digits', 'feature']), 'get_asset_quantity')
quantity_all = fields.Function(fields.Numeric(
string='Total Quantity', help='Total quantity of all assets',
readonly=True, digits=(16, Eval('quantity_digits', 4)),
states={'invisible': Eval('feature', '') != 'asset'},
depends=['quantity_digits', 'feature']), 'get_asset_quantity')
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']),
states={'invisible': ~Eval('show_performance', 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.',
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',
~Bool(Eval('company_currency', -1)),
),
}, depends=['currency_digits', 'feature', 'company_currency']),
~Eval('show_performance', False),
~Bool(Eval('company_currency', -1)))},
depends=['currency_digits', 'show_performance', 'company_currency']),
'get_asset_quantity')
# performance
diff_amount = fields.Function(fields.Numeric(string='Difference',
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']), 'get_asset_quantity')
diff_percent = fields.Function(fields.Numeric(string='Percent',
states={'invisible': ~Eval('show_performance', 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')
current_rate = fields.Function(fields.Numeric(string='Rate',
help='Rate per unit of investment based on current stock exchange price.',
states={'invisible': ~Eval('show_performance', 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')
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),
If(Eval('diff_percent', 0) < 0, 'danger',
If(Eval('diff_percent', 0) > 0, 'success', '')
), '')
),
If(Eval('diff_percent', 0) > 0,
'success', '')), '')),
]
def get_rec_name(self, name):
@ -132,7 +201,8 @@ class Book(SymbolMixin, metaclass=PoolMeta):
recname = super(Book, self).get_rec_name(name)
if self.feature == 'asset':
recname += ' | %(quantity)s %(uom_symbol)s' % {
'quantity': Report.format_number(self.quantity or 0.0, None,
'quantity': Report.format_number(
self.quantity or 0.0, None,
digits=self.quantity_digits),
'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'),
}
@ -151,9 +221,150 @@ 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()
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')
@ -161,7 +372,6 @@ class Book(SymbolMixin, metaclass=PoolMeta):
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,91 +379,275 @@ 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,
condition=tab_book.btype==tab_type.id,
).join(tab_cur,
condition=tab_book.currency==tab_cur.id,
).join(tab_asset,
condition=tab_book.asset==tab_asset.id,
).join(tab_balance,
condition=tab_book.id==tab_balance.cashbook,
type_ = 'LEFT OUTER',
).join(tab_rate,
condition=tab_book.asset==tab_rate.id,
type_ = 'LEFT OUTER',
query = tab_book.join(
tab_line,
condition=(tab_book.id == tab_line.cashbook),
).join(
tab_type,
condition=tab_book.btype == tab_type.id,
).join(
tab_cur,
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',
).join(
tab_rate,
condition=tab_book.asset == tab_rate.id,
type_='LEFT OUTER',
).select(
tab_book.id, # 0
Coalesce(Sum(Case(
(tab_line.date <= query_date,
tab_line.quantity_credit - tab_line.quantity_debit),
else_ = Decimal('0.0'),
else_=Decimal('0.0'),
)), Decimal('0.0')).as_('quantity'), # 1
Sum(tab_line.quantity_credit - tab_line.quantity_debit).as_('quantity_all'), # 2
Sum(
tab_line.quantity_credit -
tab_line.quantity_debit).as_('quantity_all'), # 2
Coalesce(tab_rate.rate, Decimal('0.0')).as_('rate'), # 3
tab_book.currency, # 4
tab_cur.digits.as_('currency_digits'), # 5
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
group_by=[tab_book.id, tab_rate.rate,
tab_asset.currency.as_('asset_currency'), # 8
(
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'))
return (query, tab_book)
@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 rdata[6] == rdata[7]:
uom_factor = Decimal('1.0')
else:
uom_factor = Decimal(
Uom.compute_qty(
Uom(rdata[6]), 1.0,
Uom(rdata[7]), round=False))
current_value = Currency.compute(
rdata[8],
rdata[3] * rdata[1] / uom_factor,
rdata[4])
return (record[0], {
'quantity': rdata[1],
'quantity_all': rdata[2],
'current_value': current_value,
'current_value_ref': Currency.compute(
rdata[8],
rdata[3] * rdata[1] / uom_factor,
company_currency
if company_currency is not None else rdata[8]),
'diff_amount': current_value - rdata[9],
'diff_percent': (
Decimal('100.0') * current_value /
rdata[9] - Decimal('100.0')
).quantize(Decimal(str(1/10**rdata[5])))
if rdata[9] != Decimal('0.0') else None,
'current_rate': (
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]),
})
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:
# uom-factor
if record[6] == record[7]:
uom_factor = Decimal('1.0')
else :
uom_factor = Decimal(
Uom.compute_qty(Uom(record[6]), 1.0, Uom(record[7]), round=False)
)
(book_id, values) = values_from_record(record)
current_value = Currency.compute(
record[8],
record[3] * record[1] / uom_factor,
record[4]
)
for name in values.keys():
result[name][book_id] = values[name]
values = {
'quantity': record[1],
'quantity_all': record[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],
),
'diff_amount': current_value - record[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,
'current_rate': (
current_value / record[1]
).quantize(Decimal(str(1/10**record[5]))) \
if record[1] != Decimal('0.0') else None,
}
# 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)))
for name in names:
result[name][record[0]] = values[name]
return result
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):
@ -268,8 +662,7 @@ class Book(SymbolMixin, metaclass=PoolMeta):
"""
return '%(currency)s/%(unit)s' % {
'currency': getattr(self.currency, 'symbol', '-'),
'unit': getattr(self.quantity_uom, 'symbol', '-'),
}
'unit': getattr(self.quantity_uom, 'symbol', '-')}
@fields.depends('asset', '_parent_asset.uom')
def on_change_with_asset_uomcat(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

584
line.py
View file

@ -4,28 +4,29 @@
# 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 trytond.modules.cashbook.const import DEF_NONE
from .mixin import SecondUomMixin
STATESQ1 = {
'invisible': And(
Eval('feature', '') != 'asset',
Eval('booktransf_feature', '') != 'asset',
),
Eval('booktransf_feature', '') != 'asset'),
'required': Or(
Eval('feature', '') == 'asset',
Eval('booktransf_feature', '') == 'asset',
),
Eval('booktransf_feature', '') == 'asset'),
'readonly': Or(
STATES['readonly'],
Eval('bookingtype', '').in_(['spin', 'spout']),
),
Eval('bookingtype', '').in_(['spin', 'spout'])),
}
DEPENDSQ1 = ['feature', 'booktransf_feature', 'quantity_digits', 'bookingtype']
DEPENDSQ1.extend(DEPENDS)
@ -35,68 +36,399 @@ STATESQ1B.update(STATESQ1)
STATESQ1B['invisible'] = And(
Eval('feature', '') != 'asset',
Eval('booktransf_feature', '') != 'asset',
Eval('splitline_has_quantity', False) == False,
)
~Eval('splitline_has_quantity', False))
STATESQ2 = {
'invisible': Eval('feature', '') != 'asset',
'required': Eval('feature', '') == 'asset',
}
'required': Eval('feature', '') == 'asset'}
DEPENDSQ2 = ['feature', 'quantity_digits', 'bookingtype']
class Line(SecondUomMixin, metaclass=PoolMeta):
__name__ = 'cashbook.line'
quantity = fields.Numeric(string='Quantity',
quantity = fields.Numeric(
string='Quantity',
digits=(16, Eval('quantity_digits', 4)),
states=STATESQ1B, depends=DEPENDSQ1+['splitline_has_quantity'])
quantity_credit = fields.Numeric(string='Quantity Credit',
quantity_credit = fields.Numeric(
string='Quantity Credit',
digits=(16, Eval('quantity_digits', 4)), readonly=True,
states=STATESQ2, depends=DEPENDSQ2)
quantity_debit = fields.Numeric(string='Quantity Debit',
quantity_debit = fields.Numeric(
string='Quantity Debit',
digits=(16, Eval('quantity_digits', 4)), readonly=True,
states=STATESQ2, depends=DEPENDSQ2)
quantity_digits = fields.Function(fields.Integer(string='Digits',
readonly=True, states={'invisible': True}),
quantity_digits = fields.Function(fields.Integer(
string='Digits', readonly=True, states={'invisible': True}),
'on_change_with_quantity_digits')
quantity_uom = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='product.uom'),
quantity_uom = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='product.uom'),
'on_change_with_quantity_uom')
asset_rate = fields.Function(fields.Numeric(string='Rate',
readonly=True,
asset_rate = fields.Function(fields.Numeric(
string='Rate', readonly=True,
digits=(16, If(
Eval('currency_digits', 2) > Eval('quantity_digits', 2),
Eval('currency_digits', 2), Eval('quantity_digits', 2))),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'quantity_digits', 'feature']),
states={'invisible': Eval('feature', '') != 'asset'},
depends=['currency_digits', 'quantity_digits', 'feature']),
'on_change_with_asset_rate')
quantity_balance = fields.Function(fields.Numeric(string='Quantity',
quantity_balance = fields.Function(fields.Numeric(
string='Quantity',
digits=(16, Eval('quantity_digits', 4)), readonly=True,
help='Number of shares in the cashbook up to the current row if the default sort applies.',
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature']),
help='Number of shares in the cashbook up to the current ' +
'row if the default sort applies.',
states={'invisible': Eval('feature', '') != 'asset'},
depends=['quantity_digits', 'feature']),
'on_change_with_quantity_balance')
splitline_has_quantity = fields.Function(fields.Boolean(
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_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 != DEF_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 != DEF_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 != DEF_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 != DEF_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 != DEF_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 != DEF_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 != DEF_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
"""
recname = super(Line, self).get_rec_name(name)
if self.cashbook.feature == 'asset':
credit = self.quantity_credit if self.quantity_credit is not None else Decimal('0.0')
debit = self.quantity_debit if self.quantity_debit is not None else Decimal('0.0')
credit = self.quantity_credit \
if self.quantity_credit is not None else Decimal('0.0')
debit = self.quantity_debit \
if self.quantity_debit is not None else Decimal('0.0')
recname += '|%(quantity)s %(uom_symbol)s' % {
'quantity': Report.format_number(credit - debit,
'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
@classmethod
@ -127,10 +459,12 @@ 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)
else:
type_ = getattr(
values, 'bookingtype', getattr(line, 'bookingtype', None))
quantity = getattr(values, 'quantity', None)
if (type_ is not None) and (cashbook.feature == 'asset'):
@ -145,8 +479,9 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
'quantity_debit': quantity,
'quantity_credit': Decimal('0.0'),
})
else :
else:
raise ValueError('invalid "bookingtype"')
return result
@classmethod
@ -155,54 +490,50 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
"""
result = super(Line, cls).get_counterpart_values(
line,
splitline = splitline,
values = values
)
splitline=splitline,
values=values)
line_uom = getattr(line.quantity_uom, 'id', None)
booktransf_uom = getattr(getattr(line.booktransf, 'quantity_uom', {}), 'id', None)
booktransf_uom = getattr(getattr(
line.booktransf, 'quantity_uom', {}), 'id', None)
if getattr(splitline, 'quantity', None) is not None:
# we add values to the counterpart of a splitbooking-line
asset_books = sum([
1 if splitline.feature == 'asset' else 0,
1 if getattr(splitline.booktransf, 'feature', '-') == 'asset' else 0,
1 if getattr(
splitline.booktransf, 'feature', '-') == 'asset' else 0,
])
diff_uom = False
if asset_books == 2:
diff_uom = (splitline.quantity_uom != splitline.booktransf.quantity_uom) and \
diff_uom = (
splitline.quantity_uom !=
splitline.booktransf.quantity_uom) and \
(splitline.quantity_uom is not None) and \
(splitline.booktransf.quantity_uom is not None)
result.update({
'quantity': splitline.quantity_2nd_uom \
if (asset_books == 2) and (diff_uom == True) \
'quantity': splitline.quantity_2nd_uom
if (asset_books == 2) and (diff_uom is True)
else splitline.quantity,
'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
result.update({
'quantity': None,
'quantity_2nd_uom': None,
})
else :
if line_uom is None:
'quantity_2nd_uom': splitline.quantity
if (asset_books == 2) and (diff_uom is True) else None})
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': line.quantity,
'quantity_2nd_uom': None,
})
elif line_uom == booktransf_uom:
'quantity_2nd_uom': None})
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,
})
else :
'quantity_2nd_uom': None})
else:
result.update({
'quantity': line.quantity_2nd_uom,
'quantity_2nd_uom': line.quantity,
})
'quantity_2nd_uom': line.quantity})
return result
@fields.depends('amount', 'splitlines', 'quantity')
@ -210,11 +541,57 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
""" update amount if splitlines change
"""
super(Line, self).on_change_splitlines()
quantity = sum([x.quantity for x in self.splitlines if x.quantity is not None])
quantity = sum([
x.quantity for x in self.splitlines
if x.quantity is not None])
cnt1 = sum([1 for x in self.splitlines if x.quantity is not None])
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
@ -229,9 +606,10 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
break
return result
@fields.depends('id', 'date', 'cashbook', 'feature',\
'_parent_cashbook.id', 'reconciliation', \
'_parent_reconciliation.start_quantity',\
@fields.depends(
'id', 'date', 'cashbook', 'feature',
'_parent_cashbook.id', 'reconciliation',
'_parent_reconciliation.start_quantity',
'_parent_reconciliation.state')
def on_change_with_quantity_balance(self, name=None):
""" get quantity-balance
@ -239,8 +617,8 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
Line2 = Pool().get('cashbook.line')
if self.feature == 'asset':
return Line2.get_balance_of_line(self,
field_name='quantity',
return Line2.get_balance_of_line(
self, field_name='quantity',
credit_name='quantity_credit',
debit_name='quantity_debit')
@ -258,21 +636,50 @@ 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.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', 'bookingtype', 'splitlines')
def on_change_with_quantity_digits(self, name=None):
""" get digits from cashbook
""" get digits from cashbook or related bookings
"""
digits = 0
if self.feature == 'asset':
if self.cashbook:
return self.cashbook.quantity_digits
return 4
digits = self.cashbook.quantity_digits \
if self.cashbook.quantity_digits > digits else digits
else:
if self.bookingtype in ['mvin', 'mvout']:
if self.booktransf:
if self.booktransf.feature == 'asset':
digits = self.booktransf.quantity_digits \
if self.booktransf.quantity_digits > digits \
else digits
elif self.bookingtype in ['spin', 'spout']:
for spline in (self.splitlines or []):
if spline.booktransf:
if spline.booktransf.feature == 'asset':
digits = spline.booktransf.quantity_digits \
if spline.booktransf.quantity_digits > digits \
else digits
return digits
@classmethod
def validate(cls, lines):
@ -291,17 +698,17 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
(line.quantity_debit is None):
raise UserError(gettext(
'cashbook_investment.msg_line_quantity_not_set',
linetxt = line.rec_name,
))
linetxt=line.rec_name))
# quantity and amount must with same sign
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,
))
linetxt=line.rec_name))
@classmethod
def update_values_by_splitlines(cls, lines):
@ -311,9 +718,10 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
for line in lines:
cnt1 = sum([1 for x in line.splitlines if x.quantity is not None])
quantity = sum([x.quantity or Decimal('0.0') for x in line.splitlines])
quantity = sum([
x.quantity or Decimal('0.0') for x in line.splitlines])
if (cnt1 > 0) and (quantity != line.quantity):
to_write.extend([ [line], {'quantity': quantity,} ])
to_write.extend([[line], {'quantity': quantity}])
return to_write
@classmethod
@ -322,12 +730,18 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
"""
values = super(Line, cls).add_values_from_splitlines(values)
if ('splitlines' in values.keys()) and ('quantity' not in values.keys()):
if ('splitlines' in values.keys()) and \
('quantity' not in values.keys()):
for action in values['splitlines']:
quantity = None
if action[0] == 'create':
cnt1 = sum([1 for x in action[1] if x.get('quantity', None) is not None])
quantity = sum([x.get('quantity', Decimal('0.0')) for x in action[1]])
cnt1 = sum([
1 for x in action[1]
if x.get('quantity', None) is not None
])
quantity = sum([
x.get('quantity', Decimal('0.0')) for x in action[1]
])
if cnt1 > 0:
values['quantity'] = quantity
return values
@ -342,7 +756,9 @@ class Line(SecondUomMixin, metaclass=PoolMeta):
cashbook = values.get('cashbook', None)
if cashbook:
values.update(cls.add_2nd_quantity(values, Cashbook(cashbook).quantity_uom))
values.update(cls.add_2nd_quantity(
values,
Cashbook(cashbook).quantity_uom))
return values
# end Line

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

@ -6,7 +6,6 @@
from trytond.model import fields
from trytond.pyson import Eval, Bool, Or
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.modules.product.uom import uom_conversion_digits
@ -22,38 +21,44 @@ DEPENDSQ.extend(DEPENDS)
class SecondUomMixin(object):
""" two fields for second uom: quantity, rate
"""
quantity_2nd_uom = fields.Numeric(string='Quantity Second UOM',
__slots__ = ()
quantity_2nd_uom = fields.Numeric(
string='Quantity Second UOM',
digits=(16, Eval('quantity2nd_digits', 4)),
states={
'readonly': Or(
STATESQ['readonly'],
~Bool(Eval('quantity2nd'))
),
~Bool(Eval('quantity2nd'))),
'required': Bool(Eval('quantity2nd')),
'invisible': ~Bool(Eval('quantity2nd')),
}, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd'])
factor_2nd_uom = fields.Function(fields.Numeric(string='Conversion factor',
help='Conversion factor between the units of the participating cash books.',
'invisible': ~Bool(Eval('quantity2nd'))},
depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd'])
factor_2nd_uom = fields.Function(fields.Numeric(
string='Conversion factor',
help='Conversion factor between the units of the ' +
'participating cash books.',
digits=uom_conversion_digits,
states={
'readonly': Or(
STATESQ['readonly'],
~Bool(Eval('quantity2nd'))
),
~Bool(Eval('quantity2nd'))),
'required': Bool(Eval('quantity2nd')),
'invisible': ~Bool(Eval('quantity2nd')),
}, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']),
'invisible': ~Bool(Eval('quantity2nd'))},
depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']),
'on_change_with_factor_2nd_uom', setter='set_factor_2nd_uom')
quantity2nd = fields.Function(fields.Many2One(model_name='product.uom',
quantity2nd = fields.Function(fields.Many2One(
model_name='product.uom',
string="2nd UOM", readonly=True), 'on_change_with_quantity2nd')
quantity2nd_digits = fields.Function(fields.Integer(string='2nd UOM Digits',
readonly=True), 'on_change_with_quantity2nd_digits')
quantity2nd_digits = fields.Function(fields.Integer(
string='2nd UOM Digits', readonly=True),
'on_change_with_quantity2nd_digits')
def quantize_quantity(self, value):
""" quantize for line-quantity
"""
return Decimal(value).quantize(Decimal(Decimal(1) / 10 ** self.quantity_digits))
return Decimal(value).quantize(
Decimal(Decimal(1) / 10 ** self.quantity_digits))
@classmethod
def add_2nd_quantity(cls, values, from_uom):
@ -74,12 +79,12 @@ class SecondUomMixin(object):
if booktransf.quantity_uom:
if from_uom.id != booktransf.quantity_uom.id:
# deny impossible transfer
if from_uom.category.id != booktransf.quantity_uom.category.id:
if from_uom.category.id != \
booktransf.quantity_uom.category.id:
raise UserError(gettext(
'cashbook_investment.msg_uomcat_mismatch',
cat1 = from_uom.category.rec_name,
cat2 = booktransf.quantity_uom.category.rec_name,
))
cat1=from_uom.category.rec_name,
cat2=booktransf.quantity_uom.category.rec_name))
values['quantity_2nd_uom'] = Decimal(UOM.compute_qty(
from_uom,
@ -87,8 +92,8 @@ class SecondUomMixin(object):
booktransf.quantity_uom,
round=False,
)).quantize(Decimal(
Decimal(1) / 10 ** booktransf.quantity_digits)
)
Decimal(1) /
10 ** booktransf.quantity_digits))
return values
@classmethod
@ -124,24 +129,27 @@ class SecondUomMixin(object):
if len(to_write) > 0:
Line2.write(*to_write)
@fields.depends('booktransf', '_parent_booktransf.quantity_uom', \
'quantity_uom', 'quantity_digits', 'quantity', \
@fields.depends(
'booktransf', '_parent_booktransf.quantity_uom',
'quantity_uom', 'quantity_digits', 'quantity',
'quantity_2nd_uom', 'factor_2nd_uom')
def on_change_booktransf(self):
""" update quantity_2nd_uom
"""
self.on_change_factor_2nd_uom()
@fields.depends('booktransf', '_parent_booktransf.quantity_uom', \
'quantity_uom', 'quantity_digits', 'quantity', \
@fields.depends(
'booktransf', '_parent_booktransf.quantity_uom',
'quantity_uom', 'quantity_digits', 'quantity',
'quantity_2nd_uom', 'factor_2nd_uom')
def on_change_quantity(self):
""" update quantity_2nd_uom
"""
self.on_change_factor_2nd_uom()
@fields.depends('booktransf', '_parent_booktransf.quantity_uom', \
'quantity_uom', 'quantity_digits', 'quantity', \
@fields.depends(
'booktransf', '_parent_booktransf.quantity_uom',
'quantity_uom', 'quantity_digits', 'quantity',
'quantity_2nd_uom', 'factor_2nd_uom')
def on_change_factor_2nd_uom(self):
""" update quantity_2nd_uom + factor_2nd_uom
@ -163,13 +171,13 @@ class SecondUomMixin(object):
self.quantity_uom,
float(self.quantity),
self.booktransf.quantity_uom,
round=False,
))
round=False))
if self.quantity != Decimal('0.0'):
self.factor_2nd_uom = (
self.quantity_2nd_uom / self.quantity
).quantize(Decimal(Decimal(1) / 10 ** uom_conversion_digits[1]))
else :
).quantize(Decimal(
Decimal(1) / 10 ** uom_conversion_digits[1]))
else:
self.quantity_2nd_uom = self.quantize_quantity(
self.quantity * self.factor_2nd_uom)
@ -188,7 +196,8 @@ class SecondUomMixin(object):
exp = Decimal(Decimal(1) / 10 ** uom_conversion_digits[1])
return (self.quantity_2nd_uom / self.quantity).quantize(exp)
@fields.depends('booktransf', '_parent_booktransf.quantity_uom', 'quantity_uom')
@fields.depends(
'booktransf', '_parent_booktransf.quantity_uom', 'quantity_uom')
def on_change_with_quantity2nd(self, name=None):
""" uom of transfer-target
"""

View file

@ -14,22 +14,24 @@ from decimal import Decimal
class Reconciliation(metaclass=PoolMeta):
__name__ = 'cashbook.recon'
start_quantity = fields.Numeric(string='Start Quantity',
readonly=True, digits=(16, Eval('quantity_digits', 4)),
start_quantity = fields.Numeric(
string='Start Quantity', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'required': Eval('feature', '') == 'asset',
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature'])
end_quantity = fields.Numeric(string='End Quantity',
readonly=True, digits=(16, Eval('quantity_digits', 4)),
end_quantity = fields.Numeric(
string='End Quantity', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'required': Eval('feature', '') == 'asset',
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature'])
quantity_digits = fields.Function(fields.Integer(string='Quantity Digits'),
'on_change_with_quantity_digits')
quantity_uom = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='product.uom'),
quantity_digits = fields.Function(fields.Integer(
string='Quantity Digits'), 'on_change_with_quantity_digits')
quantity_uom = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='product.uom'),
'on_change_with_quantity_uom')
def get_rec_name(self, name):
@ -37,13 +39,18 @@ class Reconciliation(metaclass=PoolMeta):
"""
recname = super(Reconciliation, self).get_rec_name(name)
if self.cashbook.feature == 'asset':
recname += ' | %(start_quantity)s %(uom_symbol)s - %(end_quantity)s %(uom_symbol)s' % {
'start_quantity': Report.format_number(self.start_quantity or 0.0, None,
recname += ' '.join([
' |',
Report.format_number(
self.start_quantity or 0.0, None,
digits=self.quantity_digits),
'end_quantity': Report.format_number(self.end_quantity or 0.0, None,
getattr(self.quantity_uom, 'symbol', '-'),
'-',
Report.format_number(
self.end_quantity or 0.0, None,
digits=self.quantity_digits),
'uom_symbol': self.quantity_uom.symbol,
}
getattr(self.quantity_uom, 'symbol', '-')
])
return recname
@fields.depends('cashbook', '_parent_cashbook.quantity_uom')
@ -59,7 +66,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
@ -78,8 +85,7 @@ class Reconciliation(metaclass=PoolMeta):
values = super(Reconciliation, cls).get_values_wfedit(reconciliation)
values.update({
'start_quantity': Decimal('0.0'),
'end_quantity': Decimal('0.0'),
})
'end_quantity': Decimal('0.0')})
return values
@classmethod
@ -94,7 +100,7 @@ class Reconciliation(metaclass=PoolMeta):
if reconciliation.predecessor:
values['start_quantity'] = reconciliation.predecessor.end_quantity
else :
else:
values['start_quantity'] = Decimal('0.0')
values['end_quantity'] = values['start_quantity']
@ -112,8 +118,7 @@ class Reconciliation(metaclass=PoolMeta):
# add quantities of already linked lines
values['end_quantity'] += sum([
x.quantity_credit - x.quantity_debit
for x in reconciliation.lines
])
for x in reconciliation.lines])
return values

View file

@ -2,7 +2,7 @@
"""
# Always prefer setuptools over distutils
from setuptools import setup, find_packages
from setuptools import setup
# To use a consistent encoding
from codecs import open
from os import path
@ -36,7 +36,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
l2 = i.strip().split(';')
if len(l2) < 4:
continue
modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]}
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
# tryton-version
major_version = 6
@ -51,19 +51,22 @@ for dep in info.get('depends', []):
prefix = modversion[dep]['prefix']
if len(modversion[dep]['max']) > 0:
requires.append('%s_%s >= %s, <= %s' %
(prefix, dep, modversion[dep]['min'], modversion[dep]['max']))
else :
requires.append('%s_%s >= %s' %
(prefix, dep, modversion[dep]['min']))
else :
requires.append('%s_%s >= %s.%s, < %s.%s' %
('trytond', dep, major_version, minor_version,
requires.append('%s_%s >= %s, <= %s' % (
prefix, dep, modversion[dep]['min'],
modversion[dep]['max']))
else:
requires.append('%s_%s >= %s' % (
prefix, dep, modversion[dep]['min']))
else:
requires.append(
'%s_%s >= %s.%s, < %s.%s' % (
'trytond', dep, major_version, minor_version,
major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' %
(major_version, minor_version, major_version, minor_version + 1))
requires.append('trytond >= %s.%s, < %s.%s' % (
major_version, minor_version, major_version, minor_version + 1))
setup(name='%s_%s' % (PREFIX, MODULE),
setup(
name='%s_%s' % (PREFIX, MODULE),
version=info.get('version', '0.0.1'),
description='Tryton module to add investment accounts to cashbook.',
long_description=long_description,
@ -97,9 +100,10 @@ setup(name='%s_%s' % (PREFIX, MODULE),
'trytond.modules.%s' % MODULE,
],
package_data={
'trytond.modules.%s' % MODULE: (info.get('xml', [])
'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

@ -22,17 +22,18 @@ STATESQ1A['readonly'] = ~And(
Eval('booktransf_feature', '') == 'asset',
))
class SplitLine(SecondUomMixin, metaclass=PoolMeta):
__name__ = 'cashbook.split'
quantity = fields.Numeric(string='Quantity',
digits=(16, Eval('quantity_digits', 4)),
quantity = fields.Numeric(
string='Quantity', digits=(16, Eval('quantity_digits', 4)),
states=STATESQ1A, depends=DEPENDSQ1)
quantity_digits = fields.Function(fields.Integer(string='Digits',
readonly=True, states={'invisible': True}),
quantity_digits = fields.Function(fields.Integer(
string='Digits', readonly=True, states={'invisible': True}),
'on_change_with_quantity_digits')
quantity_uom = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='product.uom'),
quantity_uom = fields.Function(fields.Many2One(
string='Symbol', readonly=True, model_name='product.uom'),
'on_change_with_quantity_uom')
def get_rec_name(self, name):
@ -41,29 +42,39 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta):
recname = super(SplitLine, self).get_rec_name(name)
if self.line.cashbook.feature == 'asset':
recname += '|%(quantity)s %(uom_symbol)s' % {
'quantity': Report.format_number(self.quantity or 0.0, None,
'quantity': Report.format_number(
self.quantity or 0.0, None,
digits=self.quantity_digits),
'uom_symbol': self.quantity_uom.symbol,
}
'uom_symbol': self.quantity_uom.symbol}
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.feature == 'asset':
if self.line.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
if self.booktransf:
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:
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
@ -76,7 +87,8 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta):
line = Line2(values.get('line', None))
if line:
values.update(cls.add_2nd_quantity(values, line.cashbook.quantity_uom))
values.update(cls.add_2nd_quantity(
values, line.cashbook.quantity_uom))
return values
# end SplitLine

View file

@ -4,23 +4,14 @@
import trytond.tests.test_tryton
import unittest
from trytond.modules.cashbook_investment.tests.test_book import CbInvTestCase
from trytond.modules.cashbook_investment.tests.test_reconciliation import ReconTestCase
from .test_module import CashbookInvestmentTestCase
__all__ = ['suite']
class CashbookInvestmentTestCase(\
CbInvTestCase,\
ReconTestCase,\
):
'Test cashbook-investment module'
module = 'cashbook_investment'
# end CashbookInvestmentTestCase
def suite():
suite = trytond.tests.test_tryton.suite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(CashbookInvestmentTestCase))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(
CashbookInvestmentTestCase))
return suite

3053
tests/book.py Normal file

File diff suppressed because it is too large Load diff

165
tests/reconciliation.py Normal file
View file

@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
# 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.
from trytond.tests.test_tryton import with_transaction
from trytond.pool import Pool
from trytond.transaction import Transaction
from datetime import date
from decimal import Decimal
class ReconTestCase(object):
""" test reconciliation
"""
@with_transaction()
def test_recon_set_start_quantity_by_cashbook(self):
""" set start-quantity of reconciliation from cashbook-setting
"""
pool = Pool()
Book = pool.get('cashbook.book')
Reconciliation = pool.get('cashbook.recon')
BType = pool.get('cashbook.type')
company = self.prep_company()
with Transaction().set_context({'company': company.id}):
type_depot = self.prep_type('Depot', 'D')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
asset = self.prep_asset_item(
company=company,
product=self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
book, = Book.create([{
'name': 'Asset-Book',
'btype': type_depot.id,
'company': company.id,
'currency': company.currency.id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{
'date': date(2022, 5, 28),
'date_from': date(2022, 5, 1),
'date_to': date(2022, 5, 31),
}])],
}])
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.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.0000 u - 0.0000 u')
@with_transaction()
def test_recon_set_start_quantity_by_predecessor(self):
""" set stat-quantity from end_amount of predecessor
"""
pool = Pool()
Book = pool.get('cashbook.book')
Lines = pool.get('cashbook.line')
Reconciliation = pool.get('cashbook.recon')
BType = pool.get('cashbook.type')
company = self.prep_company()
with Transaction().set_context({'company': company.id}):
type_depot = self.prep_type('Depot', 'D')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
asset = self.prep_asset_item(
company=company,
product=self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
category = self.prep_category(cattype='in')
party = self.prep_party()
book, = Book.create([{
'name': 'Asset-Book',
'btype': type_depot.id,
'company': company.id,
'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', [{
'date': date(2022, 5, 28),
'date_from': date(2022, 5, 1),
'date_to': date(2022, 5, 31),
}])],
'lines': [('create', [{
'date': date(2022, 5, 5),
'bookingtype': 'in',
'category': category.id,
'description': 'Line 1',
'amount': Decimal('5.0'),
'quantity': Decimal('1.5'),
'party': party.id,
}, {
'date': date(2022, 5, 6),
'bookingtype': 'in',
'category': category.id,
'description': 'Line 2',
'party': party.id,
'amount': Decimal('7.0'),
'quantity': Decimal('2.5'),
},])],
}])
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.000 u - 0.000 u')
self.assertEqual(len(book.reconciliations[0].lines), 0)
Lines.wfcheck(list(book.lines))
self.assertEqual(book.lines[0].quantity_balance, Decimal('1.5'))
self.assertEqual(book.lines[1].quantity_balance, Decimal('4.0'))
Reconciliation.wfcheck(list(book.reconciliations))
self.assertEqual(book.lines[0].quantity_balance, Decimal('1.5'))
self.assertEqual(book.lines[1].quantity_balance, Decimal('4.0'))
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.000 u - 4.000 u')
Reconciliation.wfdone(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].state, 'done')
recons = Reconciliation.create([{
'cashbook': book.id,
'date_from': date(2022, 5, 31),
'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.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.000 u - 4.000 u')
# end ReconTestCase

File diff suppressed because it is too large Load diff

28
tests/test_module.py Normal file
View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
# 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.
from trytond.modules.cashbook.tests.test_module import CashbookTestCase
from trytond.modules.investment.tests.test_module import InvestmentTestCase
from .yieldtest import YieldTestCase
from .book import CbInvTestCase
from .reconciliation import ReconTestCase
class CashbookInvestmentTestCase(
CashbookTestCase,
InvestmentTestCase,
CbInvTestCase,
ReconTestCase,
YieldTestCase):
'Test cashbook-investment module'
module = 'cashbook_investment'
# end CashbookInvestmentTestCase
del CashbookTestCase
del InvestmentTestCase

View file

@ -1,152 +0,0 @@
# -*- coding: utf-8 -*-
# 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.
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.pool import Pool
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from datetime import date
from decimal import Decimal
class ReconTestCase(ModuleTestCase):
'Test quantity reconciliation module'
module = 'cashbook_investment'
@with_transaction()
def test_recon_set_start_quantity_by_cashbook(self):
""" set start-quantity of reconciliation from cashbook-setting
"""
pool = Pool()
Book = pool.get('cashbook.book')
Reconciliation = pool.get('cashbook.recon')
BType = pool.get('cashbook.type')
company = self.prep_company()
type_depot = self.prep_type('Depot', 'D')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
book, = Book.create([{
'name': 'Asset-Book',
'btype': type_depot.id,
'company': company.id,
'currency': company.currency.id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{
'date': date(2022, 5, 28),
'date_from': date(2022, 5, 1),
'date_to': date(2022, 5, 31),
}])],
}])
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')
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')
@with_transaction()
def test_recon_set_start_quantity_by_predecessor(self):
""" set stat-quantity from end_amount of predecessor
"""
pool = Pool()
Book = pool.get('cashbook.book')
Lines = pool.get('cashbook.line')
Reconciliation = pool.get('cashbook.recon')
BType = pool.get('cashbook.type')
company = self.prep_company()
type_depot = self.prep_type('Depot', 'D')
BType.write(*[
[type_depot],
{
'feature': 'asset',
}])
asset = self.prep_asset_item(
company=company,
product = self.prep_asset_product(name='Product 1'))
self.assertEqual(asset.symbol, 'usd/u')
category = self.prep_category(cattype='in')
party = self.prep_party()
book, = Book.create([{
'name': 'Asset-Book',
'btype': type_depot.id,
'company': company.id,
'currency': company.currency.id,
'asset': asset.id,
'quantity_uom': asset.uom.id,
'start_date': date(2022, 5, 1),
'number_sequ': self.prep_sequence().id,
'reconciliations': [('create', [{
'date': date(2022, 5, 28),
'date_from': date(2022, 5, 1),
'date_to': date(2022, 5, 31),
}])],
'lines': [('create', [{
'date': date(2022, 5, 5),
'bookingtype': 'in',
'category': category.id,
'description': 'Line 1',
'amount': Decimal('5.0'),
'quantity': Decimal('1.5'),
'party': party.id,
}, {
'date': date(2022, 5, 6),
'bookingtype': 'in',
'category': category.id,
'description': 'Line 2',
'party': party.id,
'amount': Decimal('7.0'),
'quantity': Decimal('2.5'),
},])],
}])
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')
self.assertEqual(len(book.reconciliations[0].lines), 0)
Lines.wfcheck(list(book.lines))
self.assertEqual(book.lines[0].quantity_balance, Decimal('1.5'))
self.assertEqual(book.lines[1].quantity_balance, Decimal('4.0'))
Reconciliation.wfcheck(list(book.reconciliations))
self.assertEqual(book.lines[0].quantity_balance, Decimal('1.5'))
self.assertEqual(book.lines[1].quantity_balance, Decimal('4.0'))
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')
Reconciliation.wfdone(list(book.reconciliations))
self.assertEqual(book.reconciliations[0].state, 'done')
recons = Reconciliation.create([{
'cashbook': book.id,
'date_from': date(2022, 5, 31),
'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')
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')
# end ReconTestCase

1486
tests/yieldtest.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.11
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.31;6.0.999;mds
investment;6.0.25;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>