diff --git a/README.rst b/README.rst index 67199fe..47bcc7c 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/__init__.py b/__init__.py index a5cbea0..b4c75e7 100644 --- a/__init__.py +++ b/__init__.py @@ -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') diff --git a/asset.py b/asset.py new file mode 100644 index 0000000..f805724 --- /dev/null +++ b/asset.py @@ -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 diff --git a/assetsetting.py b/assetsetting.py new file mode 100644 index 0000000..b5981dd --- /dev/null +++ b/assetsetting.py @@ -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 diff --git a/assetsetting.xml b/assetsetting.xml new file mode 100644 index 0000000..5b3e284 --- /dev/null +++ b/assetsetting.xml @@ -0,0 +1,53 @@ + + + + + + + cashbook.assetconf + form + assetconf_form + + + + Asset setting + cashbook.assetconf + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/book.py b/book.py index 332d514..7a14895 100644 --- a/book.py +++ b/book.py @@ -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'), + 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'), '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,17 +221,157 @@ 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') BookType = pool.get('cashbook.type') Line = pool.get('cashbook.line') Asset = pool.get('investment.asset') Currency = pool.get('currency.currency') - Uom = pool.get('product.uom') tab_book = CBook.__table__() tab_type = BookType.__table__() tab_line = Line.__table__() @@ -169,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_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'), - ) - cursor.execute(*query) - records = cursor.fetchall() + where=(tab_type.feature == 'asset')) + return (query, tab_book) - for record in records: + @classmethod + def get_asset_quantity(cls, cashbooks, names): + """ get quantities + field: quantity, quantity_all, current_value, + current_value_ref, diff_amount, diff_percent, + current_rate, purchase_amount + """ + pool = Pool() + CBook = pool.get('cashbook.book') + Uom = pool.get('product.uom') + Currency = pool.get('currency.currency') + IrDate = pool.get('ir.date') + MemCache = pool.get('cashbook.memcache') + cursor = Transaction().connection.cursor() + context = Transaction().context + (query, tab_book) = cls.get_asset_quantity_sql() + + company_currency = CBook.default_currency() + result = { + x: {y.id: None for y in cashbooks} + for x in [ + 'quantity', 'quantity_all', 'current_value', + 'current_value_ref', 'diff_amount', 'diff_percent', + 'current_rate', 'purchase_amount', 'purchase_amount_ref', + 'digits'] + } + + cache_keys = { + x.id: MemCache.get_key_by_record( + name='get_asset_quantity', + record=x, + query=[{ + 'model': 'cashbook.line', + 'query': [('cashbook.parent', 'child_of', [x.id])], + }, { + 'model': 'currency.currency.rate', + 'query': [('currency.id', '=', x.currency.id)], + 'cachekey' if ENA_CURRKEY else 'disabled': + CACHEKEY_CURRENCY % x.currency.id, + }, { + 'model': 'investment.rate', + 'query': [('asset.id', '=', x.asset.id)], + 'cachekey' if ENA_ASSETKEY else 'disabled': + CACHEKEY_ASSETRATE % x.asset.id, + } if x.asset is not None else {}], + addkeys=[ + str(company_currency), + str(context.get('qdate', IrDate.today()).toordinal()), + ]) + for x in cashbooks + } + + # read from cache + (todo_cashbook, result) = MemCache.read_from_cache( + cashbooks, cache_keys, names, result) + if len(todo_cashbook) == 0: + return result + + def values_from_record(rdata): + """ compute values for record + """ # uom-factor - if record[6] == record[7]: + if rdata[6] == rdata[7]: uom_factor = Decimal('1.0') - else : + else: uom_factor = Decimal( - Uom.compute_qty(Uom(record[6]), 1.0, Uom(record[7]), round=False) - ) + Uom.compute_qty( + Uom(rdata[6]), 1.0, + Uom(rdata[7]), round=False)) current_value = Currency.compute( - record[8], - record[3] * record[1] / uom_factor, - record[4] - ) - - values = { - 'quantity': record[1], - 'quantity_all': record[2], + 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( - 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], + 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 / \ - record[9] - Decimal('100.0') - ).quantize(Decimal(str(1/10**record[5]))) \ - if record[9] != Decimal('0.0') else None, + 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 / record[1] - ).quantize(Decimal(str(1/10**record[5]))) \ - if record[1] != Decimal('0.0') else None, - } + current_value / rdata[1] + ).quantize(Decimal(str(1/10**rdata[5]))) + if rdata[1] != Decimal('0.0') else None, + 'purchase_amount': record[9].quantize( + Decimal(str(1/10**rdata[5]))), + 'purchase_amount_ref': Currency.compute( + rdata[4], + record[9], + company_currency + if company_currency is not None else rdata[4]), + }) - for name in names: - result[name][record[0]] = values[name] - return result + ids_assetbooks = [x.id for x in cashbooks if x.btype is not None] + ids_nonebtypes = [x.id for x in cashbooks if x.btype is None] + + # get values of asset-cashbooks in 'cashbooks' of type=asset + if len(ids_assetbooks) > 0: + query.where &= tab_book.id.in_(ids_assetbooks) + cursor.execute(*query) + records = cursor.fetchall() + + for record in records: + (book_id, values) = values_from_record(record) + + for name in values.keys(): + result[name][book_id] = values[name] + + # add aggregated values of cashbooks without type + aggr_names = [ + 'current_value', 'current_value_ref', + 'diff_amount', 'diff_percent'] + queried_names = list(set(aggr_names).intersection(set(names))) + + if (len(queried_names) > 0) and (len(ids_nonebtypes) > 0): + # query all subordered asset-cashbooks to get values for + # cashbooks without type + + (tab_quantity, tab_book) = cls.get_asset_quantity_sql() + tab_subids = sub_ids_hierarchical('cashbook.book') + query = tab_book.join( + tab_subids, + condition=tab_book.id == tab_subids.parent, + ).join( + tab_quantity, + condition=tab_quantity.id == AnyInArray(tab_subids.subids), + ).select( + tab_book.id, + Sum(tab_quantity.quantity), # 1 + Sum(tab_quantity.quantity_all), # 2 + tab_quantity.rate, # 3 + tab_quantity.currency, # 4 + tab_quantity.currency_digits, # 5 + tab_quantity.uom, # 6 + tab_quantity.quantity_uom, # 7 + tab_quantity.asset_currency, # 8 + Sum(tab_quantity.purchase_amount), # 9 + tab_book.currency.as_('currency_book'), # 10 + where=tab_book.id.in_(ids_nonebtypes), + group_by=[ + tab_book.id, tab_quantity.rate, + tab_quantity.currency, tab_quantity.currency_digits, + tab_quantity.uom, tab_quantity.quantity_uom, + tab_quantity.asset_currency, tab_book.currency]) + cursor.execute(*query) + records = cursor.fetchall() + + for record in records: + (book_id, values) = values_from_record(record) + + for name in [ + 'current_value', 'diff_amount', + 'current_value_ref', 'purchase_amount_ref']: + if result[name][book_id] is None: + result[name][book_id] = Decimal('0.0') + + value = Decimal('0.0') + if name == 'current_value': + value = Currency.compute( + company_currency + if company_currency is not None else record[4], + values['current_value_ref'], + record[10]) + elif name == 'diff_amount': + value = Currency.compute( + company_currency + if company_currency is not None else record[4], + values['current_value_ref'] - + values['purchase_amount_ref'], + record[10]) + elif name in ['current_value_ref', 'purchase_amount_ref']: + value = values[name] + result['digits'][book_id] = record[5] + result[name][book_id] += value + + # diff_percent + for id_book in ids_nonebtypes: + c_val = result['current_value_ref'][id_book] + p_amount = result['purchase_amount_ref'][id_book] + digits = result['digits'][id_book] + + if (p_amount == Decimal('0.0')) or \ + (p_amount is None) or (c_val is None): + continue + + result['diff_percent'][id_book] = ( + Decimal('100.0') * c_val / p_amount - Decimal('100.0') + ).quantize(Decimal(str(1/10 ** digits))) + result['digits'][id_book] = None + + # store to cache + MemCache.store_result(cashbooks, cache_keys, result, todo_cashbook) + return {x: result[x] for x in names} + + @fields.depends('id') + def on_change_with_show_performance(self, name=None): + """ return True if current or subordered cashbooks + are of type=asset + """ + Book2 = Pool().get('cashbook.book') + + if Book2.search_count([ + ('btype.feature', '=', 'asset'), + ('parent', 'child_of', [self.id])]) > 0: + return True + return False @fields.depends('id') def on_change_with_asset_symbol(self, name=None): @@ -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): diff --git a/docs/settings.txt b/docs/settings.txt new file mode 100644 index 0000000..1ea1c76 --- /dev/null +++ b/docs/settings.txt @@ -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 diff --git a/line.py b/line.py index f9a4487..9497e03 100644 --- a/line.py +++ b/line.py @@ -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 \ - (splitline.quantity_uom is not None) and \ - (splitline.booktransf.quantity_uom is not None) + 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) \ - 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 + '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 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': None, - 'quantity_2nd_uom': None, - }) - else : - if line_uom is None: + 'quantity': line.quantity, + '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, - }) - elif 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.cashbook: - if self.cashbook.quantity_uom: - return self.cashbook.quantity_uom.id + if self.feature == 'asset': + if self.cashbook: + if self.cashbook.quantity_uom: + return self.cashbook.quantity_uom.id + else: + if self.booktransf: + if self.booktransf.feature == 'asset': + if self.booktransf.quantity_uom: + return self.booktransf.quantity_uom.id - @fields.depends('cashbook', '_parent_cashbook.quantity_digits') + @fields.depends( + 'feature', 'cashbook', '_parent_cashbook.quantity_digits', + 'booktransf', '_parent_booktransf.quantity_digits', + '_parent_booktransf.feature', 'bookingtype', 'splitlines') def on_change_with_quantity_digits(self, name=None): - """ get digits from cashbook + """ get digits from cashbook or related bookings """ - if self.cashbook: - return self.cashbook.quantity_digits - return 4 + digits = 0 + if self.feature == 'asset': + if self.cashbook: + 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): @@ -288,20 +695,20 @@ class Line(SecondUomMixin, metaclass=PoolMeta): # quantity must be set if (line.quantity is None) or \ (line.quantity_credit is None) or \ - (line.quantity_debit is None): + (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 - (amount_sign, a_dig, a_exp) = line.amount.as_tuple() - (quantity_sign, q_dig, q_exp) = line.quantity.as_tuple() - if amount_sign != quantity_sign: - raise UserError(gettext( - 'cashbook_investment.msg_line_sign_mismatch', - linetxt = line.rec_name, - )) + if (line.amount != Decimal('0.0')) and \ + (line.quantity != Decimal('0.0')): + (amount_sign, a_dig, a_exp) = line.amount.as_tuple() + (quantity_sign, q_dig, q_exp) = line.quantity.as_tuple() + if amount_sign != quantity_sign: + raise UserError(gettext( + 'cashbook_investment.msg_line_sign_mismatch', + linetxt=line.rec_name)) @classmethod def update_values_by_splitlines(cls, lines): @@ -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 diff --git a/locale/de.po b/locale/de.po index 9017a1d..64844fd 100644 --- a/locale/de.po +++ b/locale/de.po @@ -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." diff --git a/locale/en.po b/locale/en.po index 1cc9a42..321a596 100644 --- a/locale/en.po +++ b/locale/en.po @@ -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" + diff --git a/menu.xml b/menu.xml new file mode 100644 index 0000000..5a97d05 --- /dev/null +++ b/menu.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/mixin.py b/mixin.py index 97be83c..bab4563 100644 --- a/mixin.py +++ b/mixin.py @@ -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')) - ), + STATESQ['readonly'], + ~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')) - ), + STATESQ['readonly'], + ~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): @@ -68,18 +73,18 @@ class SecondUomMixin(object): quantity_2nd_uom = values.get('quantity_2nd_uom', None) if (quantity is not None) and (booktransf is not None) and \ - (from_uom is not None): + (from_uom is not None): if quantity_2nd_uom is None: booktransf = Cashbook(booktransf) 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 @@ -106,7 +111,7 @@ class SecondUomMixin(object): if line.booktransf is None: continue if (line.cashbook.quantity_uom is None) or \ - (line.booktransf.quantity_uom is None): + (line.booktransf.quantity_uom is None): continue if line.cashbook.quantity_uom.id == line.booktransf.quantity_uom.id: @@ -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 @@ -153,7 +161,7 @@ class SecondUomMixin(object): self.factor_2nd_uom = None return if (self.booktransf.quantity_uom is None) or \ - (self.quantity_uom is None): + (self.quantity_uom is None): return if self.factor_2nd_uom is None: @@ -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 """ @@ -196,7 +205,7 @@ class SecondUomMixin(object): if self.quantity_uom: if self.booktransf.quantity_uom: if self.quantity_uom.id != \ - self.booktransf.quantity_uom.id: + self.booktransf.quantity_uom.id: return self.booktransf.quantity_uom.id @fields.depends('booktransf', '_parent_booktransf.quantity_digits') diff --git a/reconciliation.py b/reconciliation.py index b7581e1..91f95ab 100644 --- a/reconciliation.py +++ b/reconciliation.py @@ -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'] @@ -106,14 +112,13 @@ class Reconciliation(metaclass=PoolMeta): lines_records = Line.browse(values['lines'][0][1]) values['end_quantity'] += sum([ x.quantity_credit - x.quantity_debit - for x in lines_records + for x in lines_records ]) # 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 diff --git a/setup.py b/setup.py index 086c21a..de14b11 100644 --- a/setup.py +++ b/setup.py @@ -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, - major_version, minor_version + 1)) -requires.append('trytond >= %s.%s, < %s.%s' % - (major_version, minor_version, major_version, minor_version + 1)) + 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)) -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, @@ -74,21 +77,21 @@ setup(name='%s_%s' % (PREFIX, MODULE), author_email='service@m-ds.de', license='GPL-3', classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Plugins', - 'Framework :: Tryton', - 'Intended Audience :: Developers', - 'Intended Audience :: Customer Service', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Financial and Insurance Industry', - 'Topic :: Office/Business', - 'Topic :: Office/Business :: Financial :: Accounting', - 'Natural Language :: German', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Plugins', + 'Framework :: Tryton', + 'Intended Audience :: Developers', + 'Intended Audience :: Customer Service', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Financial and Insurance Industry', + 'Topic :: Office/Business', + 'Topic :: Office/Business :: Financial :: Accounting', + 'Natural Language :: German', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', ], keywords='tryton cashbook investment', @@ -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']), }, diff --git a/splitline.py b/splitline.py index efeca8b..3718e45 100644 --- a/splitline.py +++ b/splitline.py @@ -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.quantity_uom: - return self.cashbook.quantity_uom.id + if self.line.cashbook.feature == 'asset': + if self.line.cashbook.quantity_uom: + return self.cashbook.quantity_uom.id if self.booktransf: - if self.booktransf.quantity_uom: - return self.booktransf.quantity_uom.id + if self.booktransf.feature == 'asset': + if self.booktransf.quantity_uom: + return self.booktransf.quantity_uom.id - @fields.depends('line', '_parent_line.cashbook') + @fields.depends( + 'line', '_parent_line.cashbook', 'booktransf', + '_parent_booktransf.feature', '_parent_booktransf.quantity_digits') def on_change_with_quantity_digits(self, name=None): """ get digits from cashbook """ if self.line: - return self.line.cashbook.quantity_digits + if self.line.cashbook.feature == 'asset': + return self.line.cashbook.quantity_digits + if self.booktransf: + if self.booktransf.feature == 'asset': + return self.booktransf.quantity_digits return 4 @classmethod @@ -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 diff --git a/tests/__init__.py b/tests/__init__.py index 21885a8..6b028e5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -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 diff --git a/tests/book.py b/tests/book.py new file mode 100644 index 0000000..2417b6c --- /dev/null +++ b/tests/book.py @@ -0,0 +1,3053 @@ +# -*- 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 trytond.exceptions import UserError +from datetime import date +from decimal import Decimal + + +class CbInvTestCase(object): + + @with_transaction() + def test_assetbook_create(self): + """ create cashbook, set 'btype' to asset + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + types = self.prep_type() + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + }]) + + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + + self.assertEqual(book.name, 'Book 1') + self.assertEqual( + book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 -') + self.assertEqual(book.btype.rec_name, 'CAS - Cash') + self.assertEqual(book.state, 'open') + self.assertEqual(book.state_string, 'Open') + self.assertEqual(book.feature, 'asset') + self.assertEqual(book.quantity_digits, 4) + self.assertEqual(book.show_performance, True) + + @with_transaction() + def test_assetbook_aggregated_values(self): + """ create cashbooks with hierarchy, add lines, + check values at non-type-books + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + category_in = self.prep_category(cattype='in') + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + Asset.write(*[ + [asset], + { + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('10.0'), + }, { + 'date': date(2022, 5, 2), + 'rate': Decimal('12.5'), + }])], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 12.5000 usd/u | 05/02/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + books = Book.create([{ + 'name': 'L0-Euro-None', + 'btype': None, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'childs': [('create', [{ + 'name': 'L1-Euro-Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), + }])], + }, { + 'name': 'L1-USD-Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), # 14.29 € + }])], + }, { + 'name': 'L1-Euro-Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), + 'quantity': Decimal('1.0'), + }])], + }, { + 'name': 'L1-USD-Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), # 14.29 € + 'quantity': Decimal('1.0'), + }])], + }])], + }]) + self.assertEqual(len(books), 1) + self.assertEqual(books[0].rec_name, 'L0-Euro-None') + self.assertEqual(books[0].balance, Decimal('58.57')) + self.assertEqual(books[0].balance_ref, Decimal('58.57')) + # balance of asset-books: 29,286 € + # value of asset-books: 11.9€ + 12.5USD/1.05 = 23.8€ + self.assertEqual(books[0].current_value, Decimal('23.8')) + self.assertEqual(books[0].current_value_ref, Decimal('23.8')) + self.assertEqual(books[0].diff_amount, Decimal('-5.49')) + self.assertEqual(books[0].diff_percent, Decimal('-18.74')) + + self.assertEqual(len(books[0].childs), 4) + + self.assertEqual( + books[0].childs[0].rec_name, + 'L0-Euro-None/L1-Euro-Cash | 15.00 € | Open') + self.assertEqual(books[0].childs[0].current_value, None) + self.assertEqual(books[0].childs[0].current_value_ref, None) + self.assertEqual(books[0].childs[0].diff_amount, None) + self.assertEqual(books[0].childs[0].diff_percent, None) + + self.assertEqual( + books[0].childs[1].rec_name, + 'L0-Euro-None/L1-Euro-Depot | 15.00 € | Open | 1.0000 u') + self.assertEqual( + books[0].childs[1].asset.rec_name, + 'Product 1 | 12.5000 usd/u | 05/02/2022') + self.assertEqual( + books[0].childs[1].current_value, Decimal('11.9')) + self.assertEqual( + books[0].childs[1].current_value_ref, Decimal('11.9')) + self.assertEqual( + books[0].childs[1].diff_amount, Decimal('-3.1')) + self.assertEqual( + books[0].childs[1].diff_percent, Decimal('-20.67')) + + self.assertEqual( + books[0].childs[2].rec_name, + 'L0-Euro-None/L1-USD-Cash | 15.00 usd | Open') + self.assertEqual(books[0].childs[2].current_value, None) + self.assertEqual(books[0].childs[2].current_value_ref, None) + self.assertEqual(books[0].childs[2].diff_amount, None) + self.assertEqual(books[0].childs[2].diff_percent, None) + + self.assertEqual( + books[0].childs[3].rec_name, + 'L0-Euro-None/L1-USD-Depot | 15.00 usd | Open | 1.0000 u') + self.assertEqual( + books[0].childs[3].asset.rec_name, + 'Product 1 | 12.5000 usd/u | 05/02/2022') + self.assertEqual( + books[0].childs[3].current_value, Decimal('12.5')) + self.assertEqual( + books[0].childs[3].current_value_ref, Decimal('11.9')) + self.assertEqual( + books[0].childs[3].diff_amount, Decimal('-2.5')) + self.assertEqual( + books[0].childs[3].diff_percent, Decimal('-16.67')) + + @with_transaction() + def test_assetbook_create_line(self): + """ create cashbook, add line + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + party = self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + Asset.write(*[ + [asset], + { + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('2.5'), + }, { + 'date': date(2022, 5, 2), + 'rate': Decimal('2.8'), + }])], + }]) + self.assertEqual( + asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/u') + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Text 1', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('2.5'), + 'party': party.id, + 'quantity': Decimal('1.453'), + }, { + 'date': date(2022, 5, 10), + 'description': 'Text 2', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('4.0'), + 'party': party.id, + 'quantity': Decimal('3.3'), + }], + )], + }]) + + self.assertEqual(book.name, 'Book 1') + self.assertEqual(book.rec_name, 'Book 1 | 6.50 € | Open | 4.753 u') + self.assertEqual(book.state, 'open') + self.assertEqual(book.feature, 'asset') + self.assertEqual(book.quantity_digits, 3) + self.assertEqual(book.balance_all, Decimal('6.5')) + self.assertEqual(len(book.lines), 2) + + self.assertEqual(book.lines[0].amount, Decimal('2.5')) + self.assertEqual(book.lines[0].quantity, Decimal('1.453')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.453')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity_digits, 3) + self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') + self.assertEqual(book.lines[0].current_value, Decimal('3.88')) + self.assertEqual(book.lines[0].diff_amount, Decimal('1.38')) + self.assertEqual(book.lines[0].diff_percent, Decimal('55.18')) + + self.assertEqual(book.lines[1].amount, Decimal('4.0')) + self.assertEqual(book.lines[1].quantity, Decimal('3.3')) + self.assertEqual(book.lines[1].quantity_credit, Decimal('3.3')) + self.assertEqual(book.lines[1].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[1].quantity_digits, 3) + self.assertEqual(book.lines[1].quantity_uom.symbol, 'u') + + self.assertEqual(book.symbol, '€/u') + self.assertEqual( + book.asset.rec_name, + 'Product 1 | 2.8000 usd/u | 05/02/2022') + + # wf --> check + Line.wfcheck(book.lines) + + # check quantities at cashbook + with Transaction().set_context({ + 'qdate': date(2022, 5, 5)}): + book2, = Book.browse([book]) + self.assertEqual(book.asset.rate, Decimal('2.8')) # usd + self.assertEqual(book2.quantity, Decimal('1.453')) + self.assertEqual(book2.quantity_all, Decimal('4.753')) + # 2.8 / 1.05 * 1.453 = 3.87466 + self.assertEqual(book2.current_value, Decimal('3.87')) + self.assertEqual(book2.current_value_ref, Decimal('3.87')) + + with Transaction().set_context({ + 'qdate': date(2022, 5, 12)}): + book2, = Book.browse([book]) + self.assertEqual(book2.quantity, Decimal('4.753')) + self.assertEqual(book2.quantity_all, Decimal('4.753')) + # 2.8 / 1.05 * 4.753 = 12.67466 + self.assertEqual(book2.current_value, Decimal('12.67')) + self.assertEqual(book2.current_value_ref, Decimal('12.67')) + + @with_transaction() + def test_assetbook_check_uom_and_currency_convert(self): + """ asset in US$/Ounce, cashbook in EUR/Gram + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + party = self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + gram, = Uom.search([('symbol', '=', 'g')]) + + ProdTempl.write(*[ + [asset.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }]) + + Asset.write(*[ + [asset], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }]) + self.assertEqual( + asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/oz') + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Aurum-Storage', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': gram.id, + 'quantity_digits': 3, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'store some metal', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1250.0'), + 'party': party.id, + 'quantity': Decimal('20.0'), + }], + )], + }]) + + self.assertEqual( + book.rec_name, + 'Aurum-Storage | 1,250.00 € | Open | 20.000 g') + self.assertEqual(book.balance_all, Decimal('1250.0')) + self.assertEqual(len(book.lines), 1) + + self.assertEqual(book.lines[0].amount, Decimal('1250.0')) + self.assertEqual(book.lines[0].quantity, Decimal('20.0')) + self.assertEqual(book.lines[0].quantity_uom.symbol, 'g') + + self.assertEqual(book.symbol, '€/g') + self.assertEqual( + book.asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + # check quantities at cashbook + with Transaction().set_context({ + 'qdate': date(2022, 5, 1)}): + book2, = Book.browse([book]) + self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd + self.assertEqual(book2.quantity, Decimal('20.0')) + self.assertEqual(book2.quantity_all, Decimal('20.0')) + # usd --> eur: 1750 US$ / 1.05 = 1666.666 € + # 1 ounce --> 20 gram: 1666.666 € * 20 / 28.3495 = 1175.7996 € + # better we use 'Troy Ounce': 1 oz.tr. = 31.1034768 gram + self.assertEqual(book2.current_value, Decimal('1175.80')) + self.assertEqual(book2.current_value_ref, Decimal('1175.80')) + self.assertEqual(book2.diff_amount, Decimal('-74.20')) + self.assertEqual(book2.diff_percent, Decimal('-5.94')) + self.assertEqual(book2.current_rate, Decimal('58.79')) + + @with_transaction() + def test_assetbook_check_uom_and_currency_convert2(self): + """ asset in US$/Ounce, cashbook in CHF/Gram, + company-currency EUR + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + Currency = pool.get('currency.currency') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + party = self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + gram, = Uom.search([('symbol', '=', 'g')]) + + ProdTempl.write(*[ + [asset.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }]) + + Asset.write(*[ + [asset], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }]) + self.assertEqual( + asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/oz') + chf, = Currency.create([{ + 'name': 'Swiss Franc', + 'code': 'CHF', + 'numeric_code': '756', + 'symbol': 'CHF', + 'rounding': Decimal('0.01'), + 'digits': 2, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('0.95'), + }])], + }]) + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Aurum-Storage', + 'btype': types.id, + 'company': company.id, + 'currency': chf.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': gram.id, + 'quantity_digits': 3, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'store some metal', + 'category': category.id, + 'bookingtype': 'in', + 'amount': Decimal('1250.0'), + 'party': party.id, + 'quantity': Decimal('20.0'), + }], + )], + }]) + + self.assertEqual( + book.rec_name, + 'Aurum-Storage | 1,250.00 CHF | Open | 20.000 g') + self.assertEqual(book.balance_all, Decimal('1250.0')) + self.assertEqual(len(book.lines), 1) + + self.assertEqual(book.lines[0].amount, Decimal('1250.0')) + self.assertEqual(book.lines[0].quantity, Decimal('20.0')) + self.assertEqual(book.lines[0].quantity_uom.symbol, 'g') + + self.assertEqual(book.symbol, 'CHF/g') + self.assertEqual( + book.asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + # check quantities at cashbook + with Transaction().set_context({ + 'qdate': date(2022, 5, 1)}): + book2, = Book.browse([book]) + self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd + self.assertEqual(book2.quantity, Decimal('20.0')) + self.assertEqual(book2.quantity_all, Decimal('20.0')) + # usd --> chf: 1750 US$ * 0.95 / 1.05 = 1583.333 € + # 1 ounce --> 20 gram: + # 1583.333 CHF * 20 / 28.3495 = 1117.0097 CHF + self.assertEqual( + book2.current_value, Decimal('1117.01')) # CHF + self.assertEqual( + book2.current_value_ref, Decimal('1175.80')) # EUR + self.assertEqual(book2.diff_amount, Decimal('-132.99')) + self.assertEqual(book2.diff_percent, Decimal('-10.64')) + self.assertEqual(book2.current_rate, Decimal('55.85')) + + @with_transaction() + def test_assetbook_book_uom(self): + """ check default auf uom + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + 'name': 'Asset', + 'short': 'A', + }]) + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book = Book(asset=asset, quantity_uom=None) + self.assertEqual(book.quantity_uom, None) + book.on_change_asset() + self.assertEqual(book.quantity_uom.rec_name, 'Unit') + + @with_transaction() + def test_assetbook_quantity_digits(self): + """ check selection of quantity-digits + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + category = self.prep_category(cattype='in') + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + 'name': 'Asset', + 'short': 'A', + }]) + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'name': 'Book 1', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, + 'start_date': date(2022, 5, 1), + }, { + 'name': 'Book 2', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + self.assertEqual( + books[0].rec_name, 'Book 1 | 0.00 usd | Open | 0.000 u') + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open') + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'category': category.id, + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.0'), + }])], + }]) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev|1.00 usd|- [Cat1]|1.000 u') + self.assertEqual(books[0].lines[0].quantity_digits, 3) + + Book.write(*[ + [books[1]], + { + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'category': category.id, + 'amount': Decimal('1.0'), + }])], + }]) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|Rev|1.00 usd|- [Cat1]') + self.assertEqual(books[1].lines[0].quantity_digits, 0) + + Book.write(*[ + [books[1]], + { + 'lines': [('create', [{ + 'bookingtype': 'mvin', + 'date': date(2022, 5, 1), + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.0'), + 'booktransf': books[0].id, + }])], + }]) + self.assertEqual( + books[1].lines[1].rec_name, + '05/01/2022|from|1.00 usd|- [Book 1 | ' + + '1.00 usd | Open | 1.000 u]') + self.assertEqual(books[1].lines[1].quantity_digits, 3) + + @with_transaction() + def test_assetbook_book_with_asset(self): + """ create cashbook, set 'btype' to asset + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + 'name': 'Asset', + 'short': 'A', + }]) + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + }]) + + self.assertEqual(book.name, 'Book 1') + self.assertEqual( + book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 u') + self.assertEqual(book.btype.rec_name, 'A - Asset') + self.assertEqual(book.state, 'open') + self.assertEqual(book.feature, 'asset') + self.assertEqual(book.asset.rec_name, 'Product 1 | - usd/u | -') + self.assertEqual(book.quantity_uom.rec_name, 'Unit') + + self.assertRaisesRegex( + UserError, + 'A value is required for field "Asset" in "Cashbook".', + Book.write, + *[ + [book], + { + 'asset': None, + } + ]) + + @with_transaction() + def test_assetbook_check_sign_mismatch(self): + """ create cashbook + line, bookingtype 'in', + check detection of sign mismatch between quantity and amount + """ + pool = Pool() + Book = pool.get('cashbook.book') + 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', + }]) + + category_in = self.prep_category(cattype='in') + self.prep_party() + + 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, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'buy some', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.5'), + }])], + }]) + + self.assertEqual( + book.rec_name, + 'Asset-Book | 1.00 usd | Open | 1.5000 u') + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + + self.assertRaisesRegex( + UserError, + "Quantity and Amount must with same sign for line " + + "05/01/2022|Rev|1.00 usd|buy some [Cat1].", + Book.write, + *[ + [book], + { + 'lines': [('write', [book.lines[0]], { + 'quantity': Decimal('-1.5'), + })], + } + ]) + + @with_transaction() + def test_assetbook_check_mvout(self): + """ create cashbook + line, bookingtype 'mvout' + transfer from cash to depot (buy asset, pay from cash) + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + type_cash = self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + self.prep_category(cattype='in') + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book2, = Book.create([{ + 'name': 'Asset-Book', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + self.assertEqual(book2.show_performance, True) + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer Out', + 'category': category_out.id, + 'bookingtype': 'mvout', + 'amount': Decimal('1.0'), + 'booktransf': book2.id, + 'quantity': Decimal('1.5'), + }])], + }]) + self.assertEqual(book.show_performance, False) + self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, None) + self.assertEqual(book.lines[0].quantity_debit, None) + self.assertEqual(book.lines[0].feature, 'gen') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + self.assertEqual(len(book2.lines), 0) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book' + + ' | 0.00 usd | Open | 0.0000 u]') + self.assertEqual(len(book.lines[0].references), 0) + + # update quantity + Book.write(*[ + [book], + { + 'lines': [ + ('write', + [book.lines[0]], {'quantity': Decimal('2.5')})], + }]) + self.assertEqual(book.lines[0].quantity, Decimal('2.5')) + self.assertEqual(book.lines[0].quantity_credit, None) + self.assertEqual(book.lines[0].quantity_debit, None) + + # check counterpart + self.assertEqual( + book.lines[0].booktransf.rec_name, + 'Asset-Book | 0.00 usd | Open | 0.0000 u') + self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + + # set line to 'checked', this creates the counterpart + Line.wfcheck(list(book.lines)) + + self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') + self.assertEqual(len(book.lines), 1) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | ' + + '1.00 usd | Open | 2.5000 u]') + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[0].bookingtype, 'mvout') + self.assertEqual(book.lines[0].feature, 'gen') + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('0.0')) + self.assertEqual(book.lines[0].debit, Decimal('1.0')) + self.assertEqual(book.lines[0].quantity, Decimal('2.5')) + self.assertEqual( + book.lines[0].quantity_credit, None) # feature != asset + # --> no quantity-credit/debit + self.assertEqual( + book.lines[0].quantity_debit, None) + self.assertEqual(book.lines[0].quantity_2nd_uom, None) + self.assertEqual(book.lines[0].factor_2nd_uom, None) + self.assertEqual(book.lines[0].quantity2nd, None) + self.assertEqual(book.lines[0].quantity2nd_digits, 4) + self.assertEqual(len(book.lines[0].references), 1) + self.assertEqual(book.lines[0].reference, None) + self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) + + self.assertEqual( + book2.rec_name, + 'Asset-Book | 1.00 usd | Open | 2.5000 u') + self.assertEqual(len(book2.lines), 1) + self.assertEqual( + book2.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer Out [Book 1 | ' + + '-1.00 usd | Open]|2.5000 u') + self.assertEqual(book2.lines[0].state, 'check') + self.assertEqual(book2.lines[0].bookingtype, 'mvin') + self.assertEqual(book2.lines[0].feature, 'asset') + self.assertEqual(book2.lines[0].amount, Decimal('1.0')) + self.assertEqual(book2.lines[0].credit, Decimal('1.0')) + self.assertEqual(book2.lines[0].debit, Decimal('0.0')) + self.assertEqual(book2.lines[0].quantity, Decimal('2.5')) + self.assertEqual( + book2.lines[0].quantity_credit, + Decimal('2.5')) # feature=asset + self.assertEqual( + book2.lines[0].quantity_debit, + Decimal('0.0')) # needs quantity-credit/debit + self.assertEqual(book2.lines[0].quantity_2nd_uom, None) + self.assertEqual(book2.lines[0].factor_2nd_uom, None) + self.assertEqual(book2.lines[0].quantity2nd, None) + self.assertEqual(book2.lines[0].quantity2nd_digits, 4) + self.assertEqual(book2.lines[0].asset_rate, Decimal('0.4')) + self.assertEqual( + book2.lines[0].reference.rec_name, + '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | ' + + '1.00 usd | Open | 2.5000 u]') + self.assertEqual(len(book2.lines[0].references), 0) + + l1 = list(book.lines) + l1.append(Line( + bookingtype='mvout', + amount=Decimal('2.5'), + quantity=Decimal('2.5'), + booktransf=book2, + )) + book.lines = l1 + book.lines[-1].on_change_quantity() + + @with_transaction() + def test_assetbook_check_mvout_zero_quantity(self): + """ create cashbook + line, bookingtype 'mvout' + transfer from asset-book to cash-book - zero quantity, + to book gain/loss + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + type_cash = self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book1, = Book.create([{ + 'name': 'Asset-Book', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book2, = Book.create([{ + 'name': 'Cash-Book', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + Book.write(*[ + [book1], + { + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'loss at sell', + 'category': category_out.id, + 'bookingtype': 'mvout', + 'amount': Decimal('10.0'), + 'booktransf': book2.id, + 'quantity': Decimal('0.0'), + }])], + }, + ]) + self.assertEqual( + book1.rec_name, + 'Asset-Book | -10.00 usd | Open | 0.0000 u') + self.assertEqual(len(book1.lines), 1) + self.assertEqual( + book1.lines[0].rec_name, + '05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | ' + + '0.00 usd | Open]|0.0000 u') + self.assertEqual(book2.rec_name, 'Cash-Book | 0.00 usd | Open') + self.assertEqual(len(book2.lines), 0) + + Line.wfcheck(list(book1.lines)) + + self.assertEqual( + book1.rec_name, + 'Asset-Book | -10.00 usd | Open | 0.0000 u') + self.assertEqual(len(book1.lines), 1) + self.assertEqual( + book1.lines[0].rec_name, + '05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | ' + + '10.00 usd | Open]|0.0000 u') + self.assertEqual(book2.rec_name, 'Cash-Book | 10.00 usd | Open') + self.assertEqual(len(book2.lines), 1) + self.assertEqual( + book2.lines[0].rec_name, + '05/01/2022|from|10.00 usd|loss at sell [Asset-Book | ' + + '-10.00 usd | Open | 0.0000 u]') + + @with_transaction() + def test_assetbook_check_mvin(self): + """ create cashbook + line, bookingtype 'mvin' + transfer from depot to cash (sell asset, transfer to cash) + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + type_cash = self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + self.prep_category(cattype='in') + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book2, = Book.create([{ + 'name': 'Asset-Book', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book, = Book.create([{ + 'name': 'Book 1', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer In', + 'category': category_out.id, + 'bookingtype': 'mvin', + 'amount': Decimal('1.0'), + 'booktransf': book2.id, + 'quantity': Decimal('1.5'), + }])], + }]) + self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, None) + self.assertEqual(book.lines[0].quantity_debit, None) + self.assertEqual(book.lines[0].feature, 'gen') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + self.assertEqual(book.lines[0].splitline_has_quantity, False) + self.assertEqual(len(book2.lines), 0) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | ' + + '0.00 usd | Open | 0.0000 u]') + self.assertEqual(len(book.lines[0].references), 0) + + # check counterpart + self.assertEqual( + book.lines[0].booktransf.rec_name, + 'Asset-Book | 0.00 usd | Open | 0.0000 u') + self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + + # set line to 'checked', this creates the counterpart + Line.wfcheck(list(book.lines)) + + self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') + self.assertEqual(len(book.lines), 1) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | ' + + '-1.00 usd | Open | -1.5000 u]') + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[0].bookingtype, 'mvin') + self.assertEqual(book.lines[0].feature, 'gen') + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual( + book.lines[0].quantity_credit, None) # feature != asset + self.assertEqual( + book.lines[0].quantity_debit, + None) # --> no quantity-credit/debit + self.assertEqual(book.lines[0].quantity_2nd_uom, None) + self.assertEqual(book.lines[0].factor_2nd_uom, None) + self.assertEqual(book.lines[0].quantity2nd, None) + self.assertEqual(book.lines[0].quantity2nd_digits, 4) + self.assertEqual(len(book.lines[0].references), 1) + self.assertEqual(book.lines[0].reference, None) + self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) + + self.assertEqual( + book2.rec_name, + 'Asset-Book | -1.00 usd | Open | -1.5000 u') + self.assertEqual(len(book2.lines), 1) + self.assertEqual( + book2.lines[0].rec_name, + '05/01/2022|to|-1.00 usd|Transfer In [Book 1 | ' + + '1.00 usd | Open]|-1.5000 u') + self.assertEqual(book2.lines[0].state, 'check') + self.assertEqual(book2.lines[0].bookingtype, 'mvout') + self.assertEqual(book2.lines[0].feature, 'asset') + self.assertEqual(book2.lines[0].amount, Decimal('1.0')) + self.assertEqual(book2.lines[0].credit, Decimal('0.0')) + self.assertEqual(book2.lines[0].debit, Decimal('1.0')) + self.assertEqual(book2.lines[0].quantity, Decimal('1.5')) + self.assertEqual( + book2.lines[0].quantity_credit, + Decimal('0.0')) # feature=asset + self.assertEqual( + book2.lines[0].quantity_debit, + Decimal('1.5')) # needs quantity-credit/debit + self.assertEqual(book2.lines[0].quantity_2nd_uom, None) + self.assertEqual(book2.lines[0].factor_2nd_uom, None) + self.assertEqual(book2.lines[0].quantity2nd, None) + self.assertEqual(book2.lines[0].quantity2nd_digits, 4) + self.assertEqual(book2.lines[0].asset_rate, Decimal('0.6667')) + self.assertEqual( + book2.lines[0].reference.rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | ' + + '-1.00 usd | Open | -1.5000 u]') + self.assertEqual(len(book2.lines[0].references), 0) + + @with_transaction() + def test_assetbook_check_mvin_two_assetbooks(self): + """ create cashbook + line, bookingtype 'mvin' + transfer from depot to depot, equal uom on both cashbooks + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + self.prep_category(cattype='in') + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book2, = Book.create([{ + 'name': 'Asset-Book 1', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book, = Book.create([{ + 'name': 'Asset-Book 2', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer In', + 'category': category_out.id, + 'bookingtype': 'mvin', + 'amount': Decimal('1.0'), + 'booktransf': book2.id, + 'quantity': Decimal('1.5'), + }])], + }]) + self.assertEqual( + book.rec_name, + 'Asset-Book 2 | 1.00 usd | Open | 1.5000 u') + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[0].feature, 'asset') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + self.assertEqual(len(book2.lines), 0) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '0.00 usd | Open | 0.0000 u]|1.5000 u') + self.assertEqual(len(book.lines[0].references), 0) + + # check counterpart + self.assertEqual( + book.lines[0].booktransf.rec_name, + 'Asset-Book 1 | 0.00 usd | Open | 0.0000 u') + self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + + # set line to 'checked', this creates the counterpart + Line.wfcheck(list(book.lines)) + + self.assertEqual( + book.rec_name, + 'Asset-Book 2 | 1.00 usd | Open | 1.5000 u') + self.assertEqual(len(book.lines), 1) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '-1.00 usd | Open | -1.5000 u]|1.5000 u') + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[0].bookingtype, 'mvin') + self.assertEqual(book.lines[0].feature, 'asset') + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity_2nd_uom, None) + self.assertEqual(book.lines[0].factor_2nd_uom, None) + self.assertEqual(book.lines[0].quantity2nd, None) + self.assertEqual(book.lines[0].quantity2nd_digits, 4) + self.assertEqual(len(book.lines[0].references), 1) + self.assertEqual(book.lines[0].reference, None) + self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) + + self.assertEqual( + book2.rec_name, + 'Asset-Book 1 | -1.00 usd | Open | -1.5000 u') + self.assertEqual(len(book2.lines), 1) + self.assertEqual( + book2.lines[0].rec_name, + '05/01/2022|to|-1.00 usd|Transfer In [Asset-Book 2 | ' + + '1.00 usd | Open | 1.5000 u]|-1.5000 u') + self.assertEqual(book2.lines[0].state, 'check') + self.assertEqual(book2.lines[0].bookingtype, 'mvout') + self.assertEqual(book2.lines[0].feature, 'asset') + self.assertEqual(book2.lines[0].amount, Decimal('1.0')) + self.assertEqual(book2.lines[0].credit, Decimal('0.0')) + self.assertEqual(book2.lines[0].debit, Decimal('1.0')) + self.assertEqual(book2.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book2.lines[0].quantity_credit, Decimal('0.0')) + self.assertEqual(book2.lines[0].quantity_debit, Decimal('1.5')) + self.assertEqual(book2.lines[0].quantity_2nd_uom, None) + self.assertEqual(book2.lines[0].factor_2nd_uom, None) + self.assertEqual(book2.lines[0].quantity2nd, None) + self.assertEqual(book2.lines[0].quantity2nd_digits, 4) + self.assertEqual(book2.lines[0].asset_rate, Decimal('0.6667')) + self.assertEqual( + book2.lines[0].reference.rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '-1.00 usd | Open | -1.5000 u]|1.5000 u') + self.assertEqual(len(book2.lines[0].references), 0) + + @with_transaction() + def test_assetbook_check_mvin_two_assetbooks_diff_uom_equal_uomcat(self): + """ create cashbook + line, bookingtype 'mvin' + transfer from depot to depot, + different uom (equal uom-category) on both cashbooks + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + UOM = pool.get('product.uom') + ProdTempl = pool.get('product.template') + Asset = pool.get('investment.asset') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + self.prep_category(cattype='in') + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset1 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + asset2 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 2')) + + uom_grams = UOM.search([('symbol', '=', 'g')])[0] + uom_ounce = UOM.search([('symbol', '=', 'oz')])[0] + ProdTempl.write(*[ + [asset1.product.template], + { + 'default_uom': uom_grams.id, + }, + [asset2.product.template], + { + 'default_uom': uom_ounce.id, + }, + ]) + + Asset.write(*[ + [asset1], + { + 'uom': uom_grams.id, + }, + [asset2], + { + 'uom': uom_ounce.id, + }, + ]) + self.assertEqual(asset1.symbol, 'usd/g') + self.assertEqual(asset2.symbol, 'usd/oz') + + book2, = Book.create([{ + 'name': 'Asset-Book 1', + 'btype': type_depot.id, + 'asset': asset1.id, + 'quantity_uom': asset1.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book, = Book.create([{ + 'name': 'Asset-Book 2', + 'btype': type_depot.id, + 'asset': asset2.id, + 'quantity_uom': asset2.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer In', + 'category': category_out.id, + 'bookingtype': 'mvin', + 'amount': Decimal('1.0'), + 'booktransf': book2.id, + 'quantity': Decimal('1.5'), + }])], + }]) + self.assertEqual( + book.rec_name, + 'Asset-Book 2 | 1.00 usd | Open | 1.5000 oz') + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity_uom.symbol, 'oz') + self.assertEqual(book.lines[0].quantity_uom.factor, 0.028349523125) + self.assertEqual(book.lines[0].quantity2nd.symbol, 'g') + self.assertEqual(book.lines[0].quantity2nd.factor, 0.001) + self.assertEqual( + book.lines[0].quantity_2nd_uom, + Decimal('42.5243')) # 1.5 oz --> g + self.assertEqual( + book.lines[0].factor_2nd_uom, + Decimal('28.349533333333')) + self.assertEqual(book.lines[0].quantity2nd_digits, 4) + self.assertEqual(book.lines[0].feature, 'asset') + self.assertEqual(len(book2.lines), 0) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '0.00 usd | Open | 0.0000 g]|1.5000 oz') + self.assertEqual(len(book.lines[0].references), 0) + + # check counterpart + self.assertEqual( + book.lines[0].booktransf.rec_name, + 'Asset-Book 1 | 0.00 usd | Open | 0.0000 g') + self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') + self.assertEqual(book.lines[0].booktransf_feature, 'asset') + + # set line to 'checked', this creates the counterpart + Line.wfcheck(list(book.lines)) + + self.assertEqual( + book.rec_name, + 'Asset-Book 2 | 1.00 usd | Open | 1.5000 oz') + self.assertEqual(len(book.lines), 1) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '-1.00 usd | Open | -42.5243 g]|1.5000 oz') + self.assertEqual(book.lines[0].state, 'check') + self.assertEqual(book.lines[0].bookingtype, 'mvin') + self.assertEqual(book.lines[0].feature, 'asset') + self.assertEqual(book.lines[0].amount, Decimal('1.0')) + self.assertEqual(book.lines[0].credit, Decimal('1.0')) + self.assertEqual(book.lines[0].debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) + self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) + self.assertEqual(book.lines[0].quantity_2nd_uom, Decimal('42.5243')) + self.assertEqual( + book.lines[0].factor_2nd_uom, + Decimal('28.349533333333')) + self.assertEqual(book.lines[0].quantity2nd.symbol, 'g') + self.assertEqual(book.lines[0].quantity2nd.factor, 0.001) + self.assertEqual(book.lines[0].quantity2nd_digits, 4) + self.assertEqual(len(book.lines[0].references), 1) + self.assertEqual(book.lines[0].reference, None) + self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) + + self.assertEqual( + book2.rec_name, + 'Asset-Book 1 | -1.00 usd | Open | -42.5243 g') + self.assertEqual(len(book2.lines), 1) + self.assertEqual( + book2.lines[0].rec_name, + '05/01/2022|to|-1.00 usd|Transfer In [Asset-Book 2 | ' + + '1.00 usd | Open | 1.5000 oz]|-42.5243 g') + self.assertEqual(book2.lines[0].state, 'check') + self.assertEqual(book2.lines[0].bookingtype, 'mvout') + self.assertEqual(book2.lines[0].feature, 'asset') + self.assertEqual(book2.lines[0].amount, Decimal('1.0')) + self.assertEqual(book2.lines[0].credit, Decimal('0.0')) + self.assertEqual(book2.lines[0].debit, Decimal('1.0')) + self.assertEqual(book2.lines[0].quantity, Decimal('42.5243')) + self.assertEqual(book2.lines[0].quantity_credit, Decimal('0.0')) + self.assertEqual(book2.lines[0].quantity_debit, Decimal('42.5243')) + self.assertEqual(book2.lines[0].quantity_2nd_uom, Decimal('1.5')) + self.assertEqual( + book2.lines[0].factor_2nd_uom, + Decimal('0.035273949248')) + self.assertEqual(book2.lines[0].quantity2nd.symbol, 'oz') + self.assertEqual(book2.lines[0].quantity2nd.factor, 0.028349523125) + self.assertEqual(book2.lines[0].quantity2nd_digits, 4) + self.assertEqual(book2.lines[0].asset_rate, Decimal('0.0235')) + self.assertEqual( + book2.lines[0].reference.rec_name, + '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | ' + + '-1.00 usd | Open | -42.5243 g]|1.5000 oz') + self.assertEqual(len(book2.lines[0].references), 0) + + @with_transaction() + def test_assetbook_check_mvin_two_assetbooks_diff_uom_diff_uomcat(self): + """ create cashbook + line, bookingtype 'mvin' + transfer from depot to depot, + different uom (different uom-category) on both cashbooks + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + UOM = pool.get('product.uom') + ProdTempl = pool.get('product.template') + Asset = pool.get('investment.asset') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + self.prep_category(cattype='in') + category_out = self.prep_category( + name='Out Category', cattype='out') + self.prep_party() + + asset1 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + asset2 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 2')) + + uom_grams = UOM.search([('symbol', '=', 'g')])[0] + uom_min = UOM.search([('symbol', '=', 'min')])[0] + ProdTempl.write(*[ + [asset1.product.template], + { + 'default_uom': uom_grams.id, + }, + [asset2.product.template], + { + 'default_uom': uom_min.id, + }, + ]) + + Asset.write(*[ + [asset1], + { + 'uom': uom_grams.id, + }, + [asset2], + { + 'uom': uom_min.id, + }, + ]) + self.assertEqual(asset1.symbol, 'usd/g') + self.assertEqual(asset2.symbol, 'usd/min') + + book2, = Book.create([{ + 'name': 'Asset-Book 1', + 'btype': type_depot.id, + 'asset': asset1.id, + 'quantity_uom': asset1.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book, = Book.create([{ + 'name': 'Asset-Book 2', + 'btype': type_depot.id, + 'asset': asset2.id, + 'quantity_uom': asset2.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + self.assertRaisesRegex( + UserError, + r'Cannot transfer quantities between cashbooks with ' + + r'different unit-categories \(Time != Weight\).', + Book.write, + *[ + [book], + { + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer In', + 'category': category_out.id, + 'bookingtype': 'mvin', + 'amount': Decimal('1.0'), + 'booktransf': book2.id, + 'quantity': Decimal('1.5'), + }])], + }, + ]) + + @with_transaction() + def test_assetbook_split_in_category(self): + """ splitbooking incoming with asset + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [book], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + book.rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') + self.assertEqual(book.balance_all, Decimal('11.0')) + self.assertEqual(len(book.lines), 1) + + self.assertEqual(book.lines[0].amount, Decimal('11.0')) + self.assertEqual(book.lines[0].quantity, Decimal('4.0')) + self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') + self.assertEqual(book.lines[0].splitline_has_quantity, False) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') + + self.assertEqual(len(book.lines[0].splitlines), 2) + self.assertEqual(book.lines[0].splitlines[0].splittype, 'cat') + self.assertEqual(book.lines[0].splitlines[0].amount, Decimal('5.0')) + self.assertEqual( + book.lines[0].splitlines[0].quantity, Decimal('1.5')) + self.assertEqual(book.lines[0].splitlines[1].splittype, 'cat') + self.assertEqual(book.lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + book.lines[0].splitlines[1].quantity, Decimal('2.5')) + + @with_transaction() + def test_assetbook_split_in_category_and_assetbook(self): + """ splitbooking incoming to asset-cahbook, + from category and asset-cashbook + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book 2', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'from cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | 11.00 usd | Open | 4.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual(books[0].lines[0].amount, Decimal('11.0')) + self.assertEqual(books[0].lines[0].quantity, Decimal('4.0')) + self.assertEqual(books[0].lines[0].quantity_uom.symbol, 'u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') + self.assertEqual( + books[0].lines[0].splitlines[0].amount, Decimal('5.0')) + self.assertEqual( + books[0].lines[0].splitlines[0].quantity, + Decimal('1.5')) + self.assertEqual( + books[0].lines[0].splitlines[0].category.rec_name, + 'Cat1') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(len(books[0].lines[0].references), 0) + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | 11.00 usd | Open | 4.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual(books[0].lines[0].amount, Decimal('11.0')) + self.assertEqual(books[0].lines[0].quantity, Decimal('4.0')) + self.assertEqual(books[0].lines[0].quantity_uom.symbol, 'u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') + self.assertEqual( + books[0].lines[0].splitlines[0].amount, Decimal('5.0')) + self.assertEqual( + books[0].lines[0].splitlines[0].quantity, + Decimal('1.5')) + self.assertEqual( + books[0].lines[0].splitlines[0].category.rec_name, + 'Cat1') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | -6.00 usd | Open | -2.50 u') + self.assertEqual(len(books[0].lines[0].references), 1) + self.assertEqual( + books[0].lines[0].references[0].rec_name, + '05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd' + + ' | Open | 4.00 u]|-2.50 u') + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, + 'Book 2 | -6.00 usd | Open | -2.50 u') + self.assertEqual(books[1].balance_all, Decimal('-6.0')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd' + + ' | Open | 4.00 u]|-2.50 u') + + @with_transaction() + def test_assetbook_split_in_category_and_assetbook_zero_quantity(self): + """ splitbooking incoming to asset-cahbook, + from category and asset-cashbook, zero qunatity to book + gain/loss of a sell from asset-cashbook + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book 2', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'gain on sell', + 'category': category.id, + 'quantity': Decimal('0.0'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'transfer zero quantity', + 'booktransf': books[1].id, + 'quantity': Decimal('0.0'), + }])], + }])], + }]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | 11.00 usd | Open | 0.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | 0.00 usd' + + ' | Open | 0.00 u]|0.00 u') + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(len(books[0].lines[0].references), 0) + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | 11.00 usd | Open | 0.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | -6.00 usd' + + ' | Open | 0.00 u]|0.00 u') + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | -6.00 usd | Open | 0.00 u') + self.assertEqual(len(books[0].lines[0].references), 1) + self.assertEqual( + books[0].lines[0].references[0].rec_name, + '05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | ' + + '11.00 usd | Open | 0.00 u]|0.00 u') + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, + 'Book 2 | -6.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('-6.0')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | ' + + '11.00 usd | Open | 0.00 u]|0.00 u') + + @with_transaction() + def test_assetbook_split_in_catergory_asset_diff_unit(self): + """ splitbooking incoming to asset-cahbook, + from category and asset-cashbook with different + unit + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + gram, = Uom.search([('symbol', '=', 'g')]) + + ProdTempl.write(*[ + [asset.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }]) + + Asset.write(*[ + [asset], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }]) + self.assertEqual( + asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/oz') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book ounce|usd', + 'btype': types.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': ounce.id, + 'quantity_digits': 3, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book gram|euro', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': gram.id, + 'quantity_digits': 3, + }]) + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') + self.assertEqual(books[0].balance_all, Decimal('0.0')) + self.assertEqual(len(books[0].lines), 0) + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | 0.00 € | Open | 0.000 g') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'from cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 11.00 usd | Open | 4.000 oz') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.000 oz') + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|from category [Cat1]|1.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|from cashbook [Book gram|euro | 0.00 € | ' + + 'Open | 0.000 g]|2.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity_2nd_uom, + Decimal('70.874')) + + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | 0.00 € | Open | 0.000 g') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 11.00 usd | Open | 4.000 oz') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.000 oz') + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|from category [Cat1]|1.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|from cashbook [Book gram|euro | -5.71 € | ' + + 'Open | -70.874 g]|2.500 oz') + + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | -5.71 € | Open | -70.874 g') + self.assertEqual(books[1].balance_all, Decimal('-5.71')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|to|-5.71 €|from cashbook [Book ounce|usd | ' + + '11.00 usd | Open | 4.000 oz]|-70.874 g') + + @with_transaction() + def test_assetbook_split_in_catergory_asset_diff_unit_diff_cat(self): + """ splitbooking incoming to asset-cahbook, + from category and asset-cashbook with different + unit and different uom-category + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + self.prep_party() + asset1 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + asset2 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product Liter')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + liter, = Uom.search([('symbol', '=', 'l')]) + + ProdTempl.write(*[ + [asset1.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }, + [asset2.product.template], + { + 'default_uom': liter.id, + 'name': 'Liquid', + }, + ]) + + Asset.write(*[ + [asset1], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }, + [asset2], + { + 'uom': liter.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('10.0'), + }, ])], + }, + ]) + self.assertEqual( + asset1.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + self.assertEqual( + asset2.rec_name, 'Liquid | 10.0000 usd/l | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset1.symbol, 'usd/oz') + self.assertEqual(asset2.symbol, 'usd/l') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book ounce|usd', + 'btype': types.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset1.id, + 'quantity_uom': ounce.id, + 'quantity_digits': 3, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book liter|euro', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset2.id, + 'quantity_uom': liter.id, + 'quantity_digits': 3, + }]) + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') + self.assertEqual(books[0].balance_all, Decimal('0.0')) + self.assertEqual(len(books[0].lines), 0) + self.assertEqual( + books[1].rec_name, + 'Book liter|euro | 0.00 € | Open | 0.000 l') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + self.assertRaisesRegex( + UserError, + r"Cannot transfer quantities between cashbooks with " + + r"different unit-categories \(Weight != Volume\).", + Book.write, + *[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'from cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }, + ]) + + @with_transaction() + def test_assetbook_split_out_category(self): + """ splitbooking outgoing with asset + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='out') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book, = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [book], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'to category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'cat', + 'description': 'to category', + 'category': category.id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + book.rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') + self.assertEqual(book.balance_all, Decimal('-11.0')) + self.assertEqual(len(book.lines), 1) + self.assertEqual(book.lines[0].splitline_has_quantity, False) + self.assertEqual( + book.lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') + + self.assertEqual(len(book.lines[0].splitlines), 2) + self.assertEqual( + book.lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') + self.assertEqual( + book.lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to category [Cat1]|2.50 u') + + @with_transaction() + def test_assetbook_split_out_category_and_assetbook(self): + """ splitbooking outgoing, + from asset-cashbook to asset-cashbook and to category + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='out') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book 2', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'to category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'to cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | -11.00 usd | Open | -4.00 u') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book 2 | 0.00 usd | Open ' + + '| 0.00 u]|2.50 u') + + self.assertEqual(len(books[0].lines[0].references), 0) + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual( + books[0].rec_name, + 'Book 1 | -11.00 usd | Open | -4.00 u') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book 2 | 6.00 usd | Open' + + ' | 2.50 u]|2.50 u') + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount_2nd_currency, + None) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual(books[0].lines[0].splitlines[1].quantity2nd, None) + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | 6.00 usd | Open | 2.50 u') + self.assertEqual(len(books[0].lines[0].references), 1) + self.assertEqual( + books[0].lines[0].references[0].rec_name, + '05/01/2022|from|6.00 usd|to cashbook [Book 1 | -11.00 usd' + + ' | Open | -4.00 u]|2.50 u') + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, 'Book 2 | 6.00 usd | Open | 2.50 u') + self.assertEqual(books[1].balance_all, Decimal('6.0')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|from|6.00 usd|to cashbook [Book 1 | -11.00 usd' + + ' | Open | -4.00 u]|2.50 u') + + @with_transaction() + def test_assetbook_split_out_category_and_assetbook2(self): + """ splitbooking outgoing, + from cashbook to asset-cahbook and to category + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types_asset = self.prep_type() + BType.write(*[ + [types_asset], + { + 'feature': 'asset', + 'name': 'Asset', + 'short': 'as', + }]) + types_cash = self.prep_type() + self.assertEqual(types_cash.rec_name, 'CAS - Cash') + self.assertEqual(types_asset.rec_name, 'as - Asset') + category = self.prep_category(cattype='out') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book Cash', + 'btype': types_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book Asset', + 'btype': types_asset.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'to category', + 'category': category.id, + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'to cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual(books[0].rec_name, 'Book Cash | -11.00 usd | Open') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]') + self.assertEqual(books[0].lines[0].quantity, Decimal('2.5')) + self.assertEqual(books[0].lines[0].quantity_credit, None) + self.assertEqual(books[0].lines[0].quantity_debit, None) + self.assertEqual(books[0].lines[0].quantity_2nd_uom, None) + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]') + self.assertEqual(books[0].lines[0].splitlines[0].quantity, None) + self.assertEqual( + books[0].lines[0].splitlines[0].quantity_2nd_uom, None) + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 0.00 usd | ' + + 'Open | 0.00 u]') + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity_2nd_uom, None) + + self.assertEqual(len(books[0].lines[0].references), 0) + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, + 'Book Asset | 0.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual(books[0].rec_name, 'Book Cash | -11.00 usd | Open') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + self.assertEqual(books[0].lines[0].quantity, Decimal('2.5')) + self.assertEqual(books[0].lines[0].quantity_credit, None) + self.assertEqual(books[0].lines[0].quantity_debit, None) + self.assertEqual(books[0].lines[0].quantity_2nd_uom, None) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 6.00 usd' + + ' | Open | 2.50 u]') + self.assertEqual( + books[0].lines[0].splitlines[1].amount, Decimal('6.0')) + self.assertEqual( + books[0].lines[0].splitlines[1].amount_2nd_currency, + None) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual(books[0].lines[0].splitlines[1].quantity2nd, None) + self.assertEqual( + books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book Asset | 6.00 usd | Open | 2.50 u') + self.assertEqual(len(books[0].lines[0].references), 1) + self.assertEqual( + books[0].lines[0].references[0].rec_name, + '05/01/2022|from|6.00 usd|to cashbook [Book Cash | ' + + '-11.00 usd | Open]|2.50 u') + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual( + books[1].rec_name, + 'Book Asset | 6.00 usd | Open | 2.50 u') + self.assertEqual(books[1].balance_all, Decimal('6.0')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|from|6.00 usd|to cashbook [Book Cash | ' + + '-11.00 usd | Open]|2.50 u') + + @with_transaction() + def test_assetbook_split_out_catergory_asset_diff_unit(self): + """ splitbooking outgoing to asset-cahbook, + to category and asset-cashbook with different + unit + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='out') + + self.prep_party() + asset = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + gram, = Uom.search([('symbol', '=', 'g')]) + + ProdTempl.write(*[ + [asset.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }]) + + Asset.write(*[ + [asset], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }]) + self.assertEqual( + asset.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset.symbol, 'usd/oz') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book ounce|usd', + 'btype': types.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': ounce.id, + 'quantity_digits': 3, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book gram|euro', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': gram.id, + 'quantity_digits': 3, + }]) + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') + self.assertEqual(books[0].balance_all, Decimal('0.0')) + self.assertEqual(len(books[0].lines), 0) + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | 0.00 € | Open | 0.000 g') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'to category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'to cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }]) + + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | -11.00 usd | Open | -4.000 oz') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.000 oz') + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]|1.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book gram|euro | 0.00 € | ' + + 'Open | 0.000 g]|2.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].quantity, + Decimal('2.5')) + self.assertEqual( + books[0].lines[0].splitlines[1].quantity_2nd_uom, + Decimal('70.874')) + + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | 0.00 € | Open | 0.000 g') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | -11.00 usd | Open | -4.000 oz') + self.assertEqual(books[0].balance_all, Decimal('-11.0')) + self.assertEqual(len(books[0].lines), 1) + self.assertEqual( + books[0].lines[0].rec_name, + '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.000 oz') + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual( + books[0].lines[0].splitlines[0].rec_name, + 'Exp/Sp|5.00 usd|to category [Cat1]|1.500 oz') + self.assertEqual( + books[0].lines[0].splitlines[1].rec_name, + 'Exp/Sp|6.00 usd|to cashbook [Book gram|euro | 5.71 € | ' + + 'Open | 70.874 g]|2.500 oz') + + self.assertEqual( + books[1].rec_name, + 'Book gram|euro | 5.71 € | Open | 70.874 g') + self.assertEqual(books[1].balance_all, Decimal('5.71')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual( + books[1].lines[0].rec_name, + '05/01/2022|from|5.71 €|to cashbook [Book ounce|usd | ' + + '-11.00 usd | Open | -4.000 oz]|70.874 g') + + @with_transaction() + def test_assetbook_split_out_catergory_asset_diff_unit_diff_cat(self): + """ splitbooking outgoing to asset-cahbook, + to category and asset-cashbook with different + unit and different uom-category + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='out') + + self.prep_party() + asset1 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product 1')) + asset2 = self.prep_asset_item( + company=company, + product=self.prep_asset_product(name='Product Liter')) + + # set product to ounce + ounce, = Uom.search([('symbol', '=', 'oz')]) + liter, = Uom.search([('symbol', '=', 'l')]) + + ProdTempl.write(*[ + [asset1.product.template], + { + 'default_uom': ounce.id, + 'name': 'Aurum', + }, + [asset2.product.template], + { + 'default_uom': liter.id, + 'name': 'Liquid', + }, + ]) + + Asset.write(*[ + [asset1], + { + 'uom': ounce.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }, + [asset2], + { + 'uom': liter.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('10.0'), + }, ])], + }, + ]) + self.assertEqual( + asset1.rec_name, + 'Aurum | 1,750.0000 usd/oz | 05/01/2022') + self.assertEqual( + asset2.rec_name, + 'Liquid | 10.0000 usd/l | 05/01/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + self.assertEqual(asset1.symbol, 'usd/oz') + self.assertEqual(asset2.symbol, 'usd/l') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book ounce|usd', + 'btype': types.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset1.id, + 'quantity_uom': ounce.id, + 'quantity_digits': 3, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book liter|euro', + 'btype': types.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset2.id, + 'quantity_uom': liter.id, + 'quantity_digits': 3, + }]) + self.assertEqual( + books[0].rec_name, + 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') + self.assertEqual(books[0].balance_all, Decimal('0.0')) + self.assertEqual(len(books[0].lines), 0) + self.assertEqual( + books[1].rec_name, + 'Book liter|euro | 0.00 € | Open | 0.000 l') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + self.assertRaisesRegex( + UserError, + r"Cannot transfer quantities between cashbooks with " + + r"different unit-categories \(Weight != Volume\).", + Book.write, + *[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'from category', + 'category': category.id, + 'quantity': Decimal('1.5'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'from cashbook', + 'booktransf': books[1].id, + 'quantity': Decimal('2.5'), + }])], + }])], + }, + ]) + + # TODO: + # in/out-splitbuchung + +# end CbInvTestCase diff --git a/tests/reconciliation.py b/tests/reconciliation.py new file mode 100644 index 0000000..dc891ef --- /dev/null +++ b/tests/reconciliation.py @@ -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 diff --git a/tests/test_book.py b/tests/test_book.py deleted file mode 100644 index 463b2d0..0000000 --- a/tests/test_book.py +++ /dev/null @@ -1,2278 +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 trytond.modules.cashbook.tests import CashbookTestCase -from trytond.modules.investment.tests import InvestmentTestCase -from datetime import date -from decimal import Decimal - - -class CbInvTestCase(CashbookTestCase, InvestmentTestCase): - 'Test cashbook-investment module' - module = 'cashbook_investment' - - @with_transaction() - def test_assetbook_create(self): - """ create cashbook, set 'btype' to asset - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - - types = self.prep_type() - company = self.prep_company() - book, = Book.create([{ - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - }]) - - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - - self.assertEqual(book.name, 'Book 1') - self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 -') - self.assertEqual(book.btype.rec_name, 'CAS - Cash') - self.assertEqual(book.state, 'open') - self.assertEqual(book.state_string, 'Open') - self.assertEqual(book.feature, 'asset') - self.assertEqual(book.quantity_digits, 4) - - @with_transaction() - def test_assetbook_create_line(self): - """ create cashbook, add line - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - - Asset.write(*[ - [asset], - { - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('2.5'), - }, { - 'date': date(2022, 5, 2), - 'rate': Decimal('2.8'), - }])], - }]) - self.assertEqual(asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset.symbol, 'usd/u') - - book, = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 3, - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Text 1', - 'category': category.id, - 'bookingtype': 'in', - 'amount': Decimal('2.5'), - 'party': party.id, - 'quantity': Decimal('1.453'), - }, { - 'date': date(2022, 5, 10), - 'description': 'Text 2', - 'category': category.id, - 'bookingtype': 'in', - 'amount': Decimal('4.0'), - 'party': party.id, - 'quantity': Decimal('3.3'), - }], - )], - }]) - - self.assertEqual(book.name, 'Book 1') - self.assertEqual(book.rec_name, 'Book 1 | 6.50 € | Open | 4.753 u') - self.assertEqual(book.state, 'open') - self.assertEqual(book.feature, 'asset') - self.assertEqual(book.quantity_digits, 3) - self.assertEqual(book.balance_all, Decimal('6.5')) - self.assertEqual(len(book.lines), 2) - - self.assertEqual(book.lines[0].amount, Decimal('2.5')) - self.assertEqual(book.lines[0].quantity, Decimal('1.453')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.453')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity_digits, 3) - self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') - - self.assertEqual(book.lines[1].amount, Decimal('4.0')) - self.assertEqual(book.lines[1].quantity, Decimal('3.3')) - self.assertEqual(book.lines[1].quantity_credit, Decimal('3.3')) - self.assertEqual(book.lines[1].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[1].quantity_digits, 3) - self.assertEqual(book.lines[1].quantity_uom.symbol, 'u') - - self.assertEqual(book.symbol, '€/u') - self.assertEqual(book.asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022') - - # wf --> check - Line.wfcheck(book.lines) - - # check quantities at cashbook - with Transaction().set_context({ - 'qdate': date(2022, 5, 5), - 'company': company.id, - }): - book2, = Book.browse([book]) - self.assertEqual(book.asset.rate, Decimal('2.8')) # usd - self.assertEqual(book2.quantity, Decimal('1.453')) - self.assertEqual(book2.quantity_all, Decimal('4.753')) - # 2.8 / 1.05 * 1.453 = 3.87466 - self.assertEqual(book2.current_value, Decimal('3.87')) - self.assertEqual(book2.current_value_ref, Decimal('3.87')) - - with Transaction().set_context({ - 'qdate': date(2022, 5, 12), - 'company': company.id, - }): - book2, = Book.browse([book]) - self.assertEqual(book2.quantity, Decimal('4.753')) - self.assertEqual(book2.quantity_all, Decimal('4.753')) - # 2.8 / 1.05 * 4.753 = 12.67466 - self.assertEqual(book2.current_value, Decimal('12.67')) - self.assertEqual(book2.current_value_ref, Decimal('12.67')) - - @with_transaction() - def test_assetbook_check_uom_and_currency_convert(self): - """ asset in US$/Ounce, cashbook in EUR/Gram - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - gram, = Uom.search([('symbol', '=', 'g')]) - - ProdTempl.write(*[ - [asset.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }]) - - Asset.write(*[ - [asset], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }]) - self.assertEqual(asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset.symbol, 'usd/oz') - - book, = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Aurum-Storage', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': gram.id, - 'quantity_digits': 3, - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'store some metal', - 'category': category.id, - 'bookingtype': 'in', - 'amount': Decimal('1250.0'), - 'party': party.id, - 'quantity': Decimal('20.0'), - }], - )], - }]) - - self.assertEqual(book.rec_name, 'Aurum-Storage | 1,250.00 € | Open | 20.000 g') - self.assertEqual(book.balance_all, Decimal('1250.0')) - self.assertEqual(len(book.lines), 1) - - self.assertEqual(book.lines[0].amount, Decimal('1250.0')) - self.assertEqual(book.lines[0].quantity, Decimal('20.0')) - self.assertEqual(book.lines[0].quantity_uom.symbol, 'g') - - self.assertEqual(book.symbol, '€/g') - self.assertEqual(book.asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - # check quantities at cashbook - with Transaction().set_context({ - 'qdate': date(2022, 5, 1), - 'company': company.id, - }): - book2, = Book.browse([book]) - self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd - self.assertEqual(book2.quantity, Decimal('20.0')) - self.assertEqual(book2.quantity_all, Decimal('20.0')) - # usd --> eur: 1750 US$ / 1.05 = 1666.666 € - # 1 ounce --> 20 gram: 1666.666 € * 20 / 28.3495 = 1175.7996 € - # bette we use 'Troy Ounce': 1 oz.tr. = 31.1034768 gram - self.assertEqual(book2.current_value, Decimal('1175.80')) - self.assertEqual(book2.current_value_ref, Decimal('1175.80')) - self.assertEqual(book2.diff_amount, Decimal('-74.20')) - self.assertEqual(book2.diff_percent, Decimal('-5.94')) - self.assertEqual(book2.current_rate, Decimal('58.79')) - - @with_transaction() - def test_assetbook_check_uom_and_currency_convert2(self): - """ asset in US$/Ounce, cashbook in CHF/Gram, - company-currency EUR - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - Currency = pool.get('currency.currency') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - gram, = Uom.search([('symbol', '=', 'g')]) - - ProdTempl.write(*[ - [asset.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }]) - - Asset.write(*[ - [asset], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }]) - self.assertEqual(asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset.symbol, 'usd/oz') - chf, = Currency.create([{ - 'name': 'Swiss Franc', - 'code': 'CHF', - 'numeric_code': '756', - 'symbol': 'CHF', - 'rounding': Decimal('0.01'), - 'digits': 2, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('0.95'), - }])], - }]) - - book, = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Aurum-Storage', - 'btype': types.id, - 'company': company.id, - 'currency': chf.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': gram.id, - 'quantity_digits': 3, - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'store some metal', - 'category': category.id, - 'bookingtype': 'in', - 'amount': Decimal('1250.0'), - 'party': party.id, - 'quantity': Decimal('20.0'), - }], - )], - }]) - - self.assertEqual(book.rec_name, 'Aurum-Storage | 1,250.00 CHF | Open | 20.000 g') - self.assertEqual(book.balance_all, Decimal('1250.0')) - self.assertEqual(len(book.lines), 1) - - self.assertEqual(book.lines[0].amount, Decimal('1250.0')) - self.assertEqual(book.lines[0].quantity, Decimal('20.0')) - self.assertEqual(book.lines[0].quantity_uom.symbol, 'g') - - self.assertEqual(book.symbol, 'CHF/g') - self.assertEqual(book.asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - # check quantities at cashbook - with Transaction().set_context({ - 'qdate': date(2022, 5, 1), - 'company': company.id, - }): - book2, = Book.browse([book]) - self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd - self.assertEqual(book2.quantity, Decimal('20.0')) - self.assertEqual(book2.quantity_all, Decimal('20.0')) - # usd --> chf: 1750 US$ * 0.95 / 1.05 = 1583.333 € - # 1 ounce --> 20 gram: 1583.333 CHF * 20 / 28.3495 = 1117.0097 CHF - self.assertEqual(book2.current_value, Decimal('1117.01')) # CHF - self.assertEqual(book2.current_value_ref, Decimal('1175.80')) # EUR - self.assertEqual(book2.diff_amount, Decimal('-132.99')) - self.assertEqual(book2.diff_percent, Decimal('-10.64')) - self.assertEqual(book2.current_rate, Decimal('55.85')) - - @with_transaction() - def test_assetbook_book_uom(self): - """ check default auf uom - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - - company = self.prep_company() - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - 'name': 'Asset', - 'short': 'A', - }]) - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book = Book( - asset = asset, - quantity_uom = None, - ) - self.assertEqual(book.quantity_uom, None) - book.on_change_asset() - self.assertEqual(book.quantity_uom.rec_name, 'Unit') - - @with_transaction() - def test_assetbook_book_with_asset(self): - """ create cashbook, set 'btype' to asset - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - - company = self.prep_company() - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - 'name': 'Asset', - 'short': 'A', - }]) - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book, = Book.create([{ - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - }]) - - self.assertEqual(book.name, 'Book 1') - self.assertEqual(book.rec_name, 'Book 1 | 0.00 usd | Open | 0.0000 u') - self.assertEqual(book.btype.rec_name, 'A - Asset') - self.assertEqual(book.state, 'open') - self.assertEqual(book.feature, 'asset') - self.assertEqual(book.asset.rec_name, 'Product 1 | - usd/u | -') - self.assertEqual(book.quantity_uom.rec_name, 'Unit') - - self.assertRaisesRegex(UserError, - 'A value is required for field "Asset" in "Cashbook".', - Book.write, - *[ - [book], - { - 'asset': None, - } - ]) - - @with_transaction() - def test_assetbook_check_sign_mismatch(self): - """ create cashbook + line, bookingtype 'in', - check detection of sign mismatch between quantity and amount - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - company = self.prep_company() - party = self.prep_party() - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book, = Book.create([{ - 'name': 'Asset-Book', - 'btype': type_depot.id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'buy some', - 'category': category_in.id, - 'bookingtype': 'in', - 'amount': Decimal('1.0'), - 'quantity': Decimal('1.5'), - }])], - }]) - - self.assertEqual(book.rec_name, 'Asset-Book | 1.00 usd | Open | 1.5000 u') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - - self.assertRaisesRegex(UserError, - "Quantity and Amount must with same sign for line 05/01/2022|Rev|1.00 usd|buy some [Cat1].", - Book.write, - *[ - [book], - { - 'lines': [('write', [book.lines[0]], { - 'quantity': Decimal('-1.5'), - })], - }]) - - @with_transaction() - def test_assetbook_check_mvout(self): - """ create cashbook + line, bookingtype 'mvout' - transfer from cash to depot (buy asset, pay from cash) - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - - type_cash = self.prep_type() - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') - company = self.prep_company() - party = self.prep_party() - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book2, = Book.create([{ - 'name': 'Asset-Book', - 'btype': type_depot.id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - book, = Book.create([{ - 'name': 'Book 1', - 'btype': type_cash.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Transfer Out', - 'category': category_out.id, - 'bookingtype': 'mvout', - 'amount': Decimal('1.0'), - 'booktransf': book2.id, - 'quantity': Decimal('1.5'), - }])], - }]) - self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, None) - self.assertEqual(book.lines[0].quantity_debit, None) - self.assertEqual(book.lines[0].feature, 'gen') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - self.assertEqual(len(book2.lines), 0) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | 0.00 usd | Open | 0.0000 u]') - self.assertEqual(len(book.lines[0].references), 0) - - # update quantity - Book.write(*[ - [book], - { - 'lines': [('write', [book.lines[0]], {'quantity': Decimal('2.5')})], - }]) - self.assertEqual(book.lines[0].quantity, Decimal('2.5')) - self.assertEqual(book.lines[0].quantity_credit, None) - self.assertEqual(book.lines[0].quantity_debit, None) - - # check counterpart - self.assertEqual(book.lines[0].booktransf.rec_name, 'Asset-Book | 0.00 usd | Open | 0.0000 u') - self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - - # set line to 'checked', this creates the counterpart - Line.wfcheck(list(book.lines)) - - self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | 1.00 usd | Open | 2.5000 u]') - self.assertEqual(book.lines[0].state, 'check') - self.assertEqual(book.lines[0].bookingtype, 'mvout') - self.assertEqual(book.lines[0].feature, 'gen') - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('0.0')) - self.assertEqual(book.lines[0].debit, Decimal('1.0')) - self.assertEqual(book.lines[0].quantity, Decimal('2.5')) - self.assertEqual(book.lines[0].quantity_credit, None) # feature != asset - self.assertEqual(book.lines[0].quantity_debit, None) # --> no quantity-credit/debit - self.assertEqual(book.lines[0].quantity_2nd_uom, None) - self.assertEqual(book.lines[0].factor_2nd_uom, None) - self.assertEqual(book.lines[0].quantity2nd, None) - self.assertEqual(book.lines[0].quantity2nd_digits, 4) - self.assertEqual(len(book.lines[0].references), 1) - self.assertEqual(book.lines[0].reference, None) - self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) - - self.assertEqual(book2.rec_name, 'Asset-Book | 1.00 usd | Open | 2.5000 u') - self.assertEqual(len(book2.lines), 1) - self.assertEqual(book2.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer Out [Book 1 | -1.00 usd | Open]|2.5000 u') - self.assertEqual(book2.lines[0].state, 'check') - self.assertEqual(book2.lines[0].bookingtype, 'mvin') - self.assertEqual(book2.lines[0].feature, 'asset') - self.assertEqual(book2.lines[0].amount, Decimal('1.0')) - self.assertEqual(book2.lines[0].credit, Decimal('1.0')) - self.assertEqual(book2.lines[0].debit, Decimal('0.0')) - self.assertEqual(book2.lines[0].quantity, Decimal('2.5')) - self.assertEqual(book2.lines[0].quantity_credit, Decimal('2.5')) # feature=asset - self.assertEqual(book2.lines[0].quantity_debit, Decimal('0.0')) # needs quantity-credit/debit - self.assertEqual(book2.lines[0].quantity_2nd_uom, None) - self.assertEqual(book2.lines[0].factor_2nd_uom, None) - self.assertEqual(book2.lines[0].quantity2nd, None) - self.assertEqual(book2.lines[0].quantity2nd_digits, 4) - self.assertEqual(book2.lines[0].asset_rate, Decimal('0.4')) - self.assertEqual(book2.lines[0].reference.rec_name, - '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | 1.00 usd | Open | 2.5000 u]') - self.assertEqual(len(book2.lines[0].references), 0) - - l1 = list(book.lines) - l1.append(Line( - bookingtype = 'mvout', - amount = Decimal('2.5'), - quantity = Decimal('2.5'), - booktransf = book2, - )) - book.lines = l1 - book.lines[-1].on_change_quantity() - - @with_transaction() - def test_assetbook_check_mvin(self): - """ create cashbook + line, bookingtype 'mvin' - transfer from depot to cash (sell asset, transfer to cash) - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - - type_cash = self.prep_type() - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') - company = self.prep_company() - party = self.prep_party() - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book2, = Book.create([{ - 'name': 'Asset-Book', - 'btype': type_depot.id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - book, = Book.create([{ - 'name': 'Book 1', - 'btype': type_cash.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Transfer In', - 'category': category_out.id, - 'bookingtype': 'mvin', - 'amount': Decimal('1.0'), - 'booktransf': book2.id, - 'quantity': Decimal('1.5'), - }])], - }]) - self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, None) - self.assertEqual(book.lines[0].quantity_debit, None) - self.assertEqual(book.lines[0].feature, 'gen') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - self.assertEqual(book.lines[0].splitline_has_quantity, False) - self.assertEqual(len(book2.lines), 0) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | 0.00 usd | Open | 0.0000 u]') - self.assertEqual(len(book.lines[0].references), 0) - - # check counterpart - self.assertEqual(book.lines[0].booktransf.rec_name, 'Asset-Book | 0.00 usd | Open | 0.0000 u') - self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - - # set line to 'checked', this creates the counterpart - Line.wfcheck(list(book.lines)) - - self.assertEqual(book.rec_name, 'Book 1 | 1.00 usd | Open') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | -1.00 usd | Open | -1.5000 u]') - self.assertEqual(book.lines[0].state, 'check') - self.assertEqual(book.lines[0].bookingtype, 'mvin') - self.assertEqual(book.lines[0].feature, 'gen') - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, None) # feature != asset - self.assertEqual(book.lines[0].quantity_debit, None) # --> no quantity-credit/debit - self.assertEqual(book.lines[0].quantity_2nd_uom, None) - self.assertEqual(book.lines[0].factor_2nd_uom, None) - self.assertEqual(book.lines[0].quantity2nd, None) - self.assertEqual(book.lines[0].quantity2nd_digits, 4) - self.assertEqual(len(book.lines[0].references), 1) - self.assertEqual(book.lines[0].reference, None) - self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) - - self.assertEqual(book2.rec_name, 'Asset-Book | -1.00 usd | Open | -1.5000 u') - self.assertEqual(len(book2.lines), 1) - self.assertEqual(book2.lines[0].rec_name, - '05/01/2022|to|-1.00 usd|Transfer In [Book 1 | 1.00 usd | Open]|-1.5000 u') - self.assertEqual(book2.lines[0].state, 'check') - self.assertEqual(book2.lines[0].bookingtype, 'mvout') - self.assertEqual(book2.lines[0].feature, 'asset') - self.assertEqual(book2.lines[0].amount, Decimal('1.0')) - self.assertEqual(book2.lines[0].credit, Decimal('0.0')) - self.assertEqual(book2.lines[0].debit, Decimal('1.0')) - self.assertEqual(book2.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book2.lines[0].quantity_credit, Decimal('0.0')) # feature=asset - self.assertEqual(book2.lines[0].quantity_debit, Decimal('1.5')) # needs quantity-credit/debit - self.assertEqual(book2.lines[0].quantity_2nd_uom, None) - self.assertEqual(book2.lines[0].factor_2nd_uom, None) - self.assertEqual(book2.lines[0].quantity2nd, None) - self.assertEqual(book2.lines[0].quantity2nd_digits, 4) - self.assertEqual(book2.lines[0].asset_rate, Decimal('0.6667')) - self.assertEqual(book2.lines[0].reference.rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book | -1.00 usd | Open | -1.5000 u]') - self.assertEqual(len(book2.lines[0].references), 0) - - @with_transaction() - def test_assetbook_check_mvin_two_assetbooks(self): - """ create cashbook + line, bookingtype 'mvin' - transfer from depot to depot, equal uom on both cashbooks - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - - type_cash = self.prep_type() - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') - company = self.prep_company() - party = self.prep_party() - - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book2, = Book.create([{ - 'name': 'Asset-Book 1', - 'btype': type_depot.id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - book, = Book.create([{ - 'name': 'Asset-Book 2', - 'btype': type_depot.id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Transfer In', - 'category': category_out.id, - 'bookingtype': 'mvin', - 'amount': Decimal('1.0'), - 'booktransf': book2.id, - 'quantity': Decimal('1.5'), - }])], - }]) - self.assertEqual(book.rec_name, 'Asset-Book 2 | 1.00 usd | Open | 1.5000 u') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[0].feature, 'asset') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - self.assertEqual(len(book2.lines), 0) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | 0.00 usd | Open | 0.0000 u]|1.5000 u') - self.assertEqual(len(book.lines[0].references), 0) - - # check counterpart - self.assertEqual(book.lines[0].booktransf.rec_name, 'Asset-Book 1 | 0.00 usd | Open | 0.0000 u') - self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - - # set line to 'checked', this creates the counterpart - Line.wfcheck(list(book.lines)) - - self.assertEqual(book.rec_name, 'Asset-Book 2 | 1.00 usd | Open | 1.5000 u') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | -1.00 usd | Open | -1.5000 u]|1.5000 u') - self.assertEqual(book.lines[0].state, 'check') - self.assertEqual(book.lines[0].bookingtype, 'mvin') - self.assertEqual(book.lines[0].feature, 'asset') - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity_2nd_uom, None) - self.assertEqual(book.lines[0].factor_2nd_uom, None) - self.assertEqual(book.lines[0].quantity2nd, None) - self.assertEqual(book.lines[0].quantity2nd_digits, 4) - self.assertEqual(len(book.lines[0].references), 1) - self.assertEqual(book.lines[0].reference, None) - self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) - - self.assertEqual(book2.rec_name, 'Asset-Book 1 | -1.00 usd | Open | -1.5000 u') - self.assertEqual(len(book2.lines), 1) - self.assertEqual(book2.lines[0].rec_name, - '05/01/2022|to|-1.00 usd|Transfer In [Asset-Book 2 | 1.00 usd | Open | 1.5000 u]|-1.5000 u') - self.assertEqual(book2.lines[0].state, 'check') - self.assertEqual(book2.lines[0].bookingtype, 'mvout') - self.assertEqual(book2.lines[0].feature, 'asset') - self.assertEqual(book2.lines[0].amount, Decimal('1.0')) - self.assertEqual(book2.lines[0].credit, Decimal('0.0')) - self.assertEqual(book2.lines[0].debit, Decimal('1.0')) - self.assertEqual(book2.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book2.lines[0].quantity_credit, Decimal('0.0')) - self.assertEqual(book2.lines[0].quantity_debit, Decimal('1.5')) - self.assertEqual(book2.lines[0].quantity_2nd_uom, None) - self.assertEqual(book2.lines[0].factor_2nd_uom, None) - self.assertEqual(book2.lines[0].quantity2nd, None) - self.assertEqual(book2.lines[0].quantity2nd_digits, 4) - self.assertEqual(book2.lines[0].asset_rate, Decimal('0.6667')) - self.assertEqual(book2.lines[0].reference.rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | -1.00 usd | Open | -1.5000 u]|1.5000 u') - self.assertEqual(len(book2.lines[0].references), 0) - - @with_transaction() - def test_assetbook_check_mvin_two_assetbooks_diff_uom_equal_uomcat(self): - """ create cashbook + line, bookingtype 'mvin' - transfer from depot to depot, - different uom (equal uom-category) on both cashbooks - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - UOM = pool.get('product.uom') - ProdTempl = pool.get('product.template') - Asset = pool.get('investment.asset') - - type_cash = self.prep_type() - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') - company = self.prep_company() - party = self.prep_party() - - asset1 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - asset2 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 2')) - - uom_grams = UOM.search([('symbol', '=', 'g')])[0] - uom_ounce = UOM.search([('symbol', '=', 'oz')])[0] - ProdTempl.write(*[ - [asset1.product.template], - { - 'default_uom': uom_grams.id, - }, - [asset2.product.template], - { - 'default_uom': uom_ounce.id, - }, - ]) - - Asset.write(*[ - [asset1], - { - 'uom': uom_grams.id, - }, - [asset2], - { - 'uom': uom_ounce.id, - }, - ]) - self.assertEqual(asset1.symbol, 'usd/g') - self.assertEqual(asset2.symbol, 'usd/oz') - - book2, = Book.create([{ - 'name': 'Asset-Book 1', - 'btype': type_depot.id, - 'asset': asset1.id, - 'quantity_uom': asset1.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - book, = Book.create([{ - 'name': 'Asset-Book 2', - 'btype': type_depot.id, - 'asset': asset2.id, - 'quantity_uom': asset2.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Transfer In', - 'category': category_out.id, - 'bookingtype': 'mvin', - 'amount': Decimal('1.0'), - 'booktransf': book2.id, - 'quantity': Decimal('1.5'), - }])], - }]) - self.assertEqual(book.rec_name, 'Asset-Book 2 | 1.00 usd | Open | 1.5000 oz') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity_uom.symbol, 'oz') - self.assertEqual(book.lines[0].quantity_uom.factor, 0.028349523125) - self.assertEqual(book.lines[0].quantity2nd.symbol, 'g') - self.assertEqual(book.lines[0].quantity2nd.factor, 0.001) - self.assertEqual(book.lines[0].quantity_2nd_uom, Decimal('42.5243')) # 1.5 oz --> g - self.assertEqual(book.lines[0].factor_2nd_uom, Decimal('28.349533333333')) - self.assertEqual(book.lines[0].quantity2nd_digits, 4) - self.assertEqual(book.lines[0].feature, 'asset') - self.assertEqual(len(book2.lines), 0) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | 0.00 usd | Open | 0.0000 g]|1.5000 oz') - self.assertEqual(len(book.lines[0].references), 0) - - # check counterpart - self.assertEqual(book.lines[0].booktransf.rec_name, 'Asset-Book 1 | 0.00 usd | Open | 0.0000 g') - self.assertEqual(book.lines[0].booktransf.btype.feature, 'asset') - self.assertEqual(book.lines[0].booktransf_feature, 'asset') - - # set line to 'checked', this creates the counterpart - Line.wfcheck(list(book.lines)) - - self.assertEqual(book.rec_name, 'Asset-Book 2 | 1.00 usd | Open | 1.5000 oz') - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | -1.00 usd | Open | -42.5243 g]|1.5000 oz') - self.assertEqual(book.lines[0].state, 'check') - self.assertEqual(book.lines[0].bookingtype, 'mvin') - self.assertEqual(book.lines[0].feature, 'asset') - self.assertEqual(book.lines[0].amount, Decimal('1.0')) - self.assertEqual(book.lines[0].credit, Decimal('1.0')) - self.assertEqual(book.lines[0].debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_credit, Decimal('1.5')) - self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) - self.assertEqual(book.lines[0].quantity_2nd_uom, Decimal('42.5243')) - self.assertEqual(book.lines[0].factor_2nd_uom, Decimal('28.349533333333')) - self.assertEqual(book.lines[0].quantity2nd.symbol, 'g') - self.assertEqual(book.lines[0].quantity2nd.factor, 0.001) - self.assertEqual(book.lines[0].quantity2nd_digits, 4) - self.assertEqual(len(book.lines[0].references), 1) - self.assertEqual(book.lines[0].reference, None) - self.assertEqual(book.lines[0].references[0].id, book2.lines[0].id) - - self.assertEqual(book2.rec_name, 'Asset-Book 1 | -1.00 usd | Open | -42.5243 g') - self.assertEqual(len(book2.lines), 1) - self.assertEqual(book2.lines[0].rec_name, - '05/01/2022|to|-1.00 usd|Transfer In [Asset-Book 2 | 1.00 usd | Open | 1.5000 oz]|-42.5243 g') - self.assertEqual(book2.lines[0].state, 'check') - self.assertEqual(book2.lines[0].bookingtype, 'mvout') - self.assertEqual(book2.lines[0].feature, 'asset') - self.assertEqual(book2.lines[0].amount, Decimal('1.0')) - self.assertEqual(book2.lines[0].credit, Decimal('0.0')) - self.assertEqual(book2.lines[0].debit, Decimal('1.0')) - self.assertEqual(book2.lines[0].quantity, Decimal('42.5243')) - self.assertEqual(book2.lines[0].quantity_credit, Decimal('0.0')) - self.assertEqual(book2.lines[0].quantity_debit, Decimal('42.5243')) - self.assertEqual(book2.lines[0].quantity_2nd_uom, Decimal('1.5')) - self.assertEqual(book2.lines[0].factor_2nd_uom, Decimal('0.035273949248')) - self.assertEqual(book2.lines[0].quantity2nd.symbol, 'oz') - self.assertEqual(book2.lines[0].quantity2nd.factor, 0.028349523125) - self.assertEqual(book2.lines[0].quantity2nd_digits, 4) - self.assertEqual(book2.lines[0].asset_rate, Decimal('0.0235')) - self.assertEqual(book2.lines[0].reference.rec_name, - '05/01/2022|from|1.00 usd|Transfer In [Asset-Book 1 | -1.00 usd | Open | -42.5243 g]|1.5000 oz') - self.assertEqual(len(book2.lines[0].references), 0) - - @with_transaction() - def test_assetbook_check_mvin_two_assetbooks_diff_uom_diff_uomcat(self): - """ create cashbook + line, bookingtype 'mvin' - transfer from depot to depot, - different uom (different uom-category) on both cashbooks - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - Category = pool.get('cashbook.category') - BType = pool.get('cashbook.type') - UOM = pool.get('product.uom') - ProdTempl = pool.get('product.template') - Asset = pool.get('investment.asset') - - type_cash = self.prep_type() - type_depot = self.prep_type('Depot', 'D') - BType.write(*[ - [type_depot], - { - 'feature': 'asset', - }]) - - category_in = self.prep_category(cattype='in') - category_out = self.prep_category(name='Out Category', cattype='out') - company = self.prep_company() - party = self.prep_party() - - asset1 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - asset2 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 2')) - - uom_grams = UOM.search([('symbol', '=', 'g')])[0] - uom_min = UOM.search([('symbol', '=', 'min')])[0] - ProdTempl.write(*[ - [asset1.product.template], - { - 'default_uom': uom_grams.id, - }, - [asset2.product.template], - { - 'default_uom': uom_min.id, - }, - ]) - - Asset.write(*[ - [asset1], - { - 'uom': uom_grams.id, - }, - [asset2], - { - 'uom': uom_min.id, - }, - ]) - self.assertEqual(asset1.symbol, 'usd/g') - self.assertEqual(asset2.symbol, 'usd/min') - - book2, = Book.create([{ - 'name': 'Asset-Book 1', - 'btype': type_depot.id, - 'asset': asset1.id, - 'quantity_uom': asset1.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - book, = Book.create([{ - 'name': 'Asset-Book 2', - 'btype': type_depot.id, - 'asset': asset2.id, - 'quantity_uom': asset2.uom.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'start_date': date(2022, 5, 1), - }]) - - self.assertRaisesRegex(UserError, - r'Cannot transfer quantities between cashbooks with different unit-categories \(Time != Weight\).', - Book.write, - *[ - [book], - { - 'lines': [('create', [{ - 'date': date(2022, 5, 1), - 'description': 'Transfer In', - 'category': category_out.id, - 'bookingtype': 'mvin', - 'amount': Decimal('1.0'), - 'booktransf': book2.id, - 'quantity': Decimal('1.5'), - }])], - }, - ]) - - @with_transaction() - def test_assetbook_split_in_category(self): - """ splitbooking incoming with asset - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book, = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }]) - - Book.write(*[ - [book], - { - 'lines': [('create', [{ - 'bookingtype': 'spin', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(book.rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') - self.assertEqual(book.balance_all, Decimal('11.0')) - self.assertEqual(len(book.lines), 1) - - self.assertEqual(book.lines[0].amount, Decimal('11.0')) - self.assertEqual(book.lines[0].quantity, Decimal('4.0')) - self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') - self.assertEqual(book.lines[0].splitline_has_quantity, False) - self.assertEqual(book.lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') - - self.assertEqual(len(book.lines[0].splitlines), 2) - self.assertEqual(book.lines[0].splitlines[0].splittype, 'cat') - self.assertEqual(book.lines[0].splitlines[0].amount, Decimal('5.0')) - self.assertEqual(book.lines[0].splitlines[0].quantity, Decimal('1.5')) - self.assertEqual(book.lines[0].splitlines[1].splittype, 'cat') - self.assertEqual(book.lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(book.lines[0].splitlines[1].quantity, Decimal('2.5')) - - @with_transaction() - def test_assetbook_split_in_category_and_assetbook(self): - """ splitbooking incoming to asset-cahbook, - from category and asset-cashbook - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book 2', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }]) - - Book.write(*[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spin', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'from cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') - self.assertEqual(books[0].balance_all, Decimal('11.0')) - self.assertEqual(len(books[0].lines), 1) - - self.assertEqual(books[0].lines[0].amount, Decimal('11.0')) - self.assertEqual(books[0].lines[0].quantity, Decimal('4.0')) - self.assertEqual(books[0].lines[0].quantity_uom.symbol, 'u') - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') - self.assertEqual(books[0].lines[0].splitlines[0].amount, Decimal('5.0')) - self.assertEqual(books[0].lines[0].splitlines[0].quantity, Decimal('1.5')) - self.assertEqual(books[0].lines[0].splitlines[0].category.rec_name, 'Cat1') - self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) - self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, - 'Book 2 | 0.00 usd | Open | 0.00 u') - self.assertEqual(len(books[0].lines[0].references), 0) - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Line.wfcheck([books[0].lines[0]]) - - self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 4.00 u') - self.assertEqual(books[0].balance_all, Decimal('11.0')) - self.assertEqual(len(books[0].lines), 1) - - self.assertEqual(books[0].lines[0].amount, Decimal('11.0')) - self.assertEqual(books[0].lines[0].quantity, Decimal('4.0')) - self.assertEqual(books[0].lines[0].quantity_uom.symbol, 'u') - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.00 u') - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].splittype, 'cat') - self.assertEqual(books[0].lines[0].splitlines[0].amount, Decimal('5.0')) - self.assertEqual(books[0].lines[0].splitlines[0].quantity, Decimal('1.5')) - self.assertEqual(books[0].lines[0].splitlines[0].category.rec_name, 'Cat1') - self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) - self.assertEqual(books[0].lines[0].splitlines[1].splittype, 'tr') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, - 'Book 2 | -6.00 usd | Open | -2.50 u') - self.assertEqual(len(books[0].lines[0].references), 1) - self.assertEqual(books[0].lines[0].references[0].rec_name, - '05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open | 4.00 u]|-2.50 u') - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book 2 | -6.00 usd | Open | -2.50 u') - self.assertEqual(books[1].balance_all, Decimal('-6.0')) - self.assertEqual(len(books[1].lines), 1) - self.assertEqual(books[1].lines[0].rec_name, - '05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open | 4.00 u]|-2.50 u') - - @with_transaction() - def test_assetbook_split_in_catergory_asset_diff_unit(self): - """ splitbooking incoming to asset-cahbook, - from category and asset-cashbook with different - unit - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - gram, = Uom.search([('symbol', '=', 'g')]) - - ProdTempl.write(*[ - [asset.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }]) - - Asset.write(*[ - [asset], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }]) - self.assertEqual(asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset.symbol, 'usd/oz') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book ounce|usd', - 'btype': types.id, - 'company': company.id, - 'currency': usd.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': ounce.id, - 'quantity_digits': 3, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book gram|euro', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': gram.id, - 'quantity_digits': 3, - }]) - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') - self.assertEqual(books[0].balance_all, Decimal('0.0')) - self.assertEqual(len(books[0].lines), 0) - self.assertEqual(books[1].rec_name, 'Book gram|euro | 0.00 € | Open | 0.000 g') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Book.write(*[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spin', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'from cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 11.00 usd | Open | 4.000 oz') - self.assertEqual(books[0].balance_all, Decimal('11.0')) - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.000 oz') - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Rev/Sp|5.00 usd|from category [Cat1]|1.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Rev/Sp|6.00 usd|from cashbook [Book gram|euro | 0.00 € | Open | 0.000 g]|2.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity_2nd_uom, Decimal('70.874')) - - self.assertEqual(books[1].rec_name, 'Book gram|euro | 0.00 € | Open | 0.000 g') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Line.wfcheck([books[0].lines[0]]) - - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 11.00 usd | Open | 4.000 oz') - self.assertEqual(books[0].balance_all, Decimal('11.0')) - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Rev/Sp|11.00 usd|- [-]|4.000 oz') - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Rev/Sp|5.00 usd|from category [Cat1]|1.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Rev/Sp|6.00 usd|from cashbook [Book gram|euro | -5.71 € | Open | -70.874 g]|2.500 oz') - - self.assertEqual(books[1].rec_name, 'Book gram|euro | -5.71 € | Open | -70.874 g') - self.assertEqual(books[1].balance_all, Decimal('-5.71')) - self.assertEqual(len(books[1].lines), 1) - self.assertEqual(books[1].lines[0].rec_name, - '05/01/2022|to|-5.71 €|from cashbook [Book ounce|usd | 11.00 usd | Open | 4.000 oz]|-70.874 g') - - @with_transaction() - def test_assetbook_split_in_catergory_asset_diff_unit_diff_cat(self): - """ splitbooking incoming to asset-cahbook, - from category and asset-cashbook with different - unit and different uom-category - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='in') - - company = self.prep_company() - party = self.prep_party() - asset1 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - asset2 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product Liter')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - liter, = Uom.search([('symbol', '=', 'l')]) - - ProdTempl.write(*[ - [asset1.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }, - [asset2.product.template], - { - 'default_uom': liter.id, - 'name': 'Liquid', - }, - ]) - - Asset.write(*[ - [asset1], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }, - [asset2], - { - 'uom': liter.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('10.0'), - }, ])], - }, - ]) - self.assertEqual(asset1.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - self.assertEqual(asset2.rec_name, 'Liquid | 10.0000 usd/l | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset1.symbol, 'usd/oz') - self.assertEqual(asset2.symbol, 'usd/l') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book ounce|usd', - 'btype': types.id, - 'company': company.id, - 'currency': usd.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset1.id, - 'quantity_uom': ounce.id, - 'quantity_digits': 3, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book liter|euro', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset2.id, - 'quantity_uom': liter.id, - 'quantity_digits': 3, - }]) - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') - self.assertEqual(books[0].balance_all, Decimal('0.0')) - self.assertEqual(len(books[0].lines), 0) - self.assertEqual(books[1].rec_name, 'Book liter|euro | 0.00 € | Open | 0.000 l') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - self.assertRaisesRegex(UserError, - r"Cannot transfer quantities between cashbooks with different unit-categories \(Weight != Volume\).", - Book.write, - *[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spin', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'from cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }, - ]) - - @with_transaction() - def test_assetbook_split_out_category(self): - """ splitbooking outgoing with asset - """ - pool = Pool() - Book = pool.get('cashbook.book') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='out') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - book, = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }]) - - Book.write(*[ - [book], - { - 'lines': [('create', [{ - 'bookingtype': 'spout', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'to category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'cat', - 'description': 'to category', - 'category': category.id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(book.rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') - self.assertEqual(book.balance_all, Decimal('-11.0')) - self.assertEqual(len(book.lines), 1) - self.assertEqual(book.lines[0].splitline_has_quantity, False) - self.assertEqual(book.lines[0].rec_name, '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') - - self.assertEqual(len(book.lines[0].splitlines), 2) - self.assertEqual(book.lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') - self.assertEqual(book.lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to category [Cat1]|2.50 u') - - @with_transaction() - def test_assetbook_split_out_category_and_assetbook(self): - """ splitbooking outgoing, - from asset-cashbook to asset-cahbook and to category - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='out') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book 1', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book 2', - 'btype': types.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }]) - - Book.write(*[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spout', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'to category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'to cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(books[0].rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book 2 | 0.00 usd | Open | 0.00 u]|2.50 u') - - self.assertEqual(len(books[0].lines[0].references), 0) - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Line.wfcheck([books[0].lines[0]]) - - self.assertEqual(books[0].rec_name, 'Book 1 | -11.00 usd | Open | -4.00 u') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - self.assertEqual(len(books[0].lines), 1) - - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.00 u') - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]|1.50 u') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book 2 | 6.00 usd | Open | 2.50 u]|2.50 u') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(books[0].lines[0].splitlines[1].amount_2nd_currency, None) - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity2nd, None) - self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, - 'Book 2 | 6.00 usd | Open | 2.50 u') - self.assertEqual(len(books[0].lines[0].references), 1) - self.assertEqual(books[0].lines[0].references[0].rec_name, - '05/01/2022|from|6.00 usd|to cashbook [Book 1 | -11.00 usd | Open | -4.00 u]|2.50 u') - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book 2 | 6.00 usd | Open | 2.50 u') - self.assertEqual(books[1].balance_all, Decimal('6.0')) - self.assertEqual(len(books[1].lines), 1) - self.assertEqual(books[1].lines[0].rec_name, - '05/01/2022|from|6.00 usd|to cashbook [Book 1 | -11.00 usd | Open | -4.00 u]|2.50 u') - - @with_transaction() - def test_assetbook_split_out_category_and_assetbook2(self): - """ splitbooking outgoing, - from cashbook to asset-cahbook and to category - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types_asset = self.prep_type() - BType.write(*[ - [types_asset], - { - 'feature': 'asset', - 'name': 'Asset', - 'short': 'as', - }]) - types_cash = self.prep_type() - self.assertEqual(types_cash.rec_name, 'CAS - Cash') - self.assertEqual(types_asset.rec_name, 'as - Asset') - category = self.prep_category(cattype='out') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - self.assertEqual(asset.symbol, 'usd/u') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book Cash', - 'btype': types_cash.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book Asset', - 'btype': types_asset.id, - 'company': company.id, - 'currency': company.currency.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': asset.uom.id, - 'quantity_digits': 2, - }]) - - Book.write(*[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spout', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'to category', - 'category': category.id, - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'to cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(books[0].rec_name, 'Book Cash | -11.00 usd | Open') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]') - self.assertEqual(books[0].lines[0].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].quantity_credit, None) - self.assertEqual(books[0].lines[0].quantity_debit, None) - self.assertEqual(books[0].lines[0].quantity_2nd_uom, None) - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]') - self.assertEqual(books[0].lines[0].splitlines[0].quantity, None) - self.assertEqual(books[0].lines[0].splitlines[0].quantity_2nd_uom, None) - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 0.00 usd | Open | 0.00 u]') - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity_2nd_uom, None) - - self.assertEqual(len(books[0].lines[0].references), 0) - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book Asset | 0.00 usd | Open | 0.00 u') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Line.wfcheck([books[0].lines[0]]) - - self.assertEqual(books[0].rec_name, 'Book Cash | -11.00 usd | Open') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - self.assertEqual(len(books[0].lines), 1) - - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]') - self.assertEqual(books[0].lines[0].splitline_has_quantity, True) - self.assertEqual(books[0].lines[0].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].quantity_credit, None) - self.assertEqual(books[0].lines[0].quantity_debit, None) - self.assertEqual(books[0].lines[0].quantity_2nd_uom, None) - - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book Asset | 6.00 usd | Open | 2.50 u]') - self.assertEqual(books[0].lines[0].splitlines[1].amount, Decimal('6.0')) - self.assertEqual(books[0].lines[0].splitlines[1].amount_2nd_currency, None) - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity2nd, None) - self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, - 'Book Asset | 6.00 usd | Open | 2.50 u') - self.assertEqual(len(books[0].lines[0].references), 1) - self.assertEqual(books[0].lines[0].references[0].rec_name, - '05/01/2022|from|6.00 usd|to cashbook [Book Cash | -11.00 usd | Open]|2.50 u') - self.assertEqual(books[0].lines[0].reference, None) - - self.assertEqual(books[1].rec_name, 'Book Asset | 6.00 usd | Open | 2.50 u') - self.assertEqual(books[1].balance_all, Decimal('6.0')) - self.assertEqual(len(books[1].lines), 1) - self.assertEqual(books[1].lines[0].rec_name, - '05/01/2022|from|6.00 usd|to cashbook [Book Cash | -11.00 usd | Open]|2.50 u') - - @with_transaction() - def test_assetbook_split_out_catergory_asset_diff_unit(self): - """ splitbooking outgoing to asset-cahbook, - to category and asset-cashbook with different - unit - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='out') - - company = self.prep_company() - party = self.prep_party() - asset = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - gram, = Uom.search([('symbol', '=', 'g')]) - - ProdTempl.write(*[ - [asset.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }]) - - Asset.write(*[ - [asset], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }]) - self.assertEqual(asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset.symbol, 'usd/oz') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book ounce|usd', - 'btype': types.id, - 'company': company.id, - 'currency': usd.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': ounce.id, - 'quantity_digits': 3, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book gram|euro', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset.id, - 'quantity_uom': gram.id, - 'quantity_digits': 3, - }]) - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') - self.assertEqual(books[0].balance_all, Decimal('0.0')) - self.assertEqual(len(books[0].lines), 0) - self.assertEqual(books[1].rec_name, 'Book gram|euro | 0.00 € | Open | 0.000 g') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Book.write(*[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spout', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'to category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'to cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }]) - - self.assertEqual(books[0].rec_name, 'Book ounce|usd | -11.00 usd | Open | -4.000 oz') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.000 oz') - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]|1.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book gram|euro | 0.00 € | Open | 0.000 g]|2.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].quantity, Decimal('2.5')) - self.assertEqual(books[0].lines[0].splitlines[1].quantity_2nd_uom, Decimal('70.874')) - - self.assertEqual(books[1].rec_name, 'Book gram|euro | 0.00 € | Open | 0.000 g') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - Line.wfcheck([books[0].lines[0]]) - - self.assertEqual(books[0].rec_name, 'Book ounce|usd | -11.00 usd | Open | -4.000 oz') - self.assertEqual(books[0].balance_all, Decimal('-11.0')) - self.assertEqual(len(books[0].lines), 1) - self.assertEqual(books[0].lines[0].rec_name, - '05/01/2022|Exp/Sp|-11.00 usd|- [-]|-4.000 oz') - self.assertEqual(len(books[0].lines[0].splitlines), 2) - self.assertEqual(books[0].lines[0].splitlines[0].rec_name, - 'Exp/Sp|5.00 usd|to category [Cat1]|1.500 oz') - self.assertEqual(books[0].lines[0].splitlines[1].rec_name, - 'Exp/Sp|6.00 usd|to cashbook [Book gram|euro | 5.71 € | Open | 70.874 g]|2.500 oz') - - self.assertEqual(books[1].rec_name, 'Book gram|euro | 5.71 € | Open | 70.874 g') - self.assertEqual(books[1].balance_all, Decimal('5.71')) - self.assertEqual(len(books[1].lines), 1) - self.assertEqual(books[1].lines[0].rec_name, - '05/01/2022|from|5.71 €|to cashbook [Book ounce|usd | -11.00 usd | Open | -4.000 oz]|70.874 g') - - @with_transaction() - def test_assetbook_split_out_catergory_asset_diff_unit_diff_cat(self): - """ splitbooking outgoing to asset-cahbook, - to category and asset-cashbook with different - unit and different uom-category - """ - pool = Pool() - Book = pool.get('cashbook.book') - Line = pool.get('cashbook.line') - BType = pool.get('cashbook.type') - Asset = pool.get('investment.asset') - ProdTempl = pool.get('product.template') - Uom = pool.get('product.uom') - - types = self.prep_type() - BType.write(*[ - [types], - { - 'feature': 'asset', - }]) - category = self.prep_category(cattype='out') - - company = self.prep_company() - party = self.prep_party() - asset1 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product 1')) - asset2 = self.prep_asset_item( - company=company, - product = self.prep_asset_product(name='Product Liter')) - - # set product to ounce - ounce, = Uom.search([('symbol', '=', 'oz')]) - liter, = Uom.search([('symbol', '=', 'l')]) - - ProdTempl.write(*[ - [asset1.product.template], - { - 'default_uom': ounce.id, - 'name': 'Aurum', - }, - [asset2.product.template], - { - 'default_uom': liter.id, - 'name': 'Liquid', - }, - ]) - - Asset.write(*[ - [asset1], - { - 'uom': ounce.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('1750.0'), - }, ])], - }, - [asset2], - { - 'uom': liter.id, - 'rates': [('create', [{ - 'date': date(2022, 5, 1), - 'rate': Decimal('10.0'), - }, ])], - }, - ]) - self.assertEqual(asset1.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022') - self.assertEqual(asset2.rec_name, 'Liquid | 10.0000 usd/l | 05/01/2022') - - (usd, euro) = self.prep_2nd_currency(company) - self.assertEqual(company.currency.rec_name, 'Euro') - self.assertEqual(asset1.symbol, 'usd/oz') - self.assertEqual(asset2.symbol, 'usd/l') - - books = Book.create([{ - 'start_date': date(2022, 4, 1), - 'name': 'Book ounce|usd', - 'btype': types.id, - 'company': company.id, - 'currency': usd.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset1.id, - 'quantity_uom': ounce.id, - 'quantity_digits': 3, - }, { - 'start_date': date(2022, 4, 1), - 'name': 'Book liter|euro', - 'btype': types.id, - 'company': company.id, - 'currency': euro.id, - 'number_sequ': self.prep_sequence().id, - 'asset': asset2.id, - 'quantity_uom': liter.id, - 'quantity_digits': 3, - }]) - self.assertEqual(books[0].rec_name, 'Book ounce|usd | 0.00 usd | Open | 0.000 oz') - self.assertEqual(books[0].balance_all, Decimal('0.0')) - self.assertEqual(len(books[0].lines), 0) - self.assertEqual(books[1].rec_name, 'Book liter|euro | 0.00 € | Open | 0.000 l') - self.assertEqual(books[1].balance_all, Decimal('0.0')) - self.assertEqual(len(books[1].lines), 0) - - self.assertRaisesRegex(UserError, - r"Cannot transfer quantities between cashbooks with different unit-categories \(Weight != Volume\).", - Book.write, - *[ - [books[0]], - { - 'lines': [('create', [{ - 'bookingtype': 'spout', - 'date': date(2022, 5, 1), - 'splitlines': [('create', [{ - 'amount': Decimal('5.0'), - 'splittype': 'cat', - 'description': 'from category', - 'category': category.id, - 'quantity': Decimal('1.5'), - }, { - 'amount': Decimal('6.0'), - 'splittype': 'tr', - 'description': 'from cashbook', - 'booktransf': books[1].id, - 'quantity': Decimal('2.5'), - }])], - }])], - }, - ]) - - # TODO: - # in/out-splitbuchung - -# end CbInvTestCase diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..2b73b65 --- /dev/null +++ b/tests/test_module.py @@ -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 diff --git a/tests/test_reconciliation.py b/tests/test_reconciliation.py deleted file mode 100644 index f2a37c7..0000000 --- a/tests/test_reconciliation.py +++ /dev/null @@ -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 diff --git a/tests/yieldtest.py b/tests/yieldtest.py new file mode 100644 index 0000000..5bf99f6 --- /dev/null +++ b/tests/yieldtest.py @@ -0,0 +1,1486 @@ +# -*- 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 decimal import Decimal +from datetime import date +from trytond.pool import Pool +from trytond.transaction import Transaction +from trytond.tests.test_tryton import with_transaction + + +class YieldTestCase(object): + """ test yield + """ + def prep_yield_config(self, fee, dividend, gainloss, company): + """ add config for yield-calculation + fee: name of fee-category, + dividend: name of fee-category, + gainloss: name of cashbook for gain/loss booking + """ + pool = Pool() + Category = pool.get('cashbook.category') + Cashbook = pool.get('cashbook.book') + AssetConf = pool.get('cashbook.assetconf') + + fee_cat = Category.search([('name', '=', fee)]) + if len(fee_cat) > 0: + fee_cat = fee_cat[0] + else: + fee_cat, = Category.create([{ + 'name': fee, + 'company': company.id, + 'cattype': 'out', + }]) + + dividend_cat = Category.search([('name', '=', dividend)]) + if len(dividend_cat) > 0: + dividend_cat = dividend_cat[0] + else: + dividend_cat, = Category.create([{ + 'name': dividend, + 'company': company.id, + 'cattype': 'in', + }]) + + gainloss_book = Cashbook.search([('name', '=', gainloss)]) + if len(gainloss_book) > 0: + gainloss_book = gainloss_book[0] + else: + types = self.prep_type() + gainloss_book, = Cashbook.create([{ + 'name': gainloss, + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + as_cfg = None + with Transaction().set_context({ + 'company': company.id}): + as_cfg, = AssetConf.create([{ + 'fee_category': fee_cat.id, + 'dividend_category': dividend_cat.id, + 'gainloss_book': gainloss_book.id, + }]) + self.assertEqual(as_cfg.fee_category.rec_name, fee) + self.assertEqual(as_cfg.fee_category.cattype, 'out') + + self.assertEqual(as_cfg.dividend_category.rec_name, dividend) + self.assertEqual(as_cfg.dividend_category.cattype, 'in') + + self.assertEqual( + as_cfg.gainloss_book.rec_name, + '%s | 0.00 usd | Open' % gainloss) + return as_cfg + + @with_transaction() + def test_yield_config(self): + """ check config + """ + company = self.prep_company() + + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'GainLoss', company) + self.assertEqual(as_cfg.fee_category.rec_name, 'Fee') + self.assertEqual(as_cfg.dividend_category.rec_name, 'Dividend') + self.assertEqual( + as_cfg.gainloss_book.rec_name, + 'GainLoss | 0.00 usd | Open') + + @with_transaction() + def test_yield_fee_category_in_out(self): + """ check out-booking, category in/out + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', 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_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (1)', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + lines = Line.create([{ + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'out', + 'description': 'trade fee, payed from asset', + 'category': as_cfg.fee_category.id, + 'amount': Decimal('4.0'), + 'quantity': Decimal('0.0'), + }]) + + self.assertEqual(len(lines), 1) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp|-4.00 usd|trade fee, payed from asset' + + ' [Fee]|0.0000 u') + + self.assertEqual(lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual(lines[0].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[0].trade_fee, Decimal('4.0')) + self.assertEqual( + book_asset.rec_name, + 'Depot | 19.50 usd | Open | 3.0000 u') + + self.assertEqual(book_asset.yield_dividend_total, Decimal('23.5')) + self.assertEqual(book_asset.yield_fee_total, Decimal('4.0')) + self.assertEqual(book_asset.yield_sales, Decimal('0.0')) + self.assertEqual(book_asset.diff_amount, Decimal('-23.5')) + self.assertEqual(book_asset.yield_balance, Decimal('0.0')) + + @with_transaction() + def test_yield_fee_transfer_from_splitbooking_cash_out(self): + """ check out-booking, transfer from cash --> asset, + fee on counterpart of splitbooking + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual(len(book_asset.lines), 1) + self.assertEqual(book_cash.rec_name, 'Cash | 0.00 usd | Open') + self.assertEqual(len(book_cash.lines), 0) + + Cashbook.write(*[ + [book_cash], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 2), + 'description': 'buy shares, fee', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'buy shares', + 'quantity': Decimal('1.0'), + 'amount': Decimal('30.0'), + }, { + 'splittype': 'cat', + 'category': as_cfg.fee_category.id, + 'description': 'trade fee', + 'amount': Decimal('3.0'), + }])], + }])], + }]) + + Line.wfcheck(book_cash.lines) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 53.50 usd | Open | 4.0000 u') + self.assertEqual(len(book_asset.lines), 2) + self.assertEqual(book_cash.rec_name, 'Cash | -33.00 usd | Open') + self.assertEqual(len(book_cash.lines), 1) + + self.assertEqual( + book_asset.lines[0].rec_name, + '05/02/2022|from|30.00 usd|buy shares [Cash | ' + + '-33.00 usd | Open]|1.0000 u') + self.assertEqual( + book_asset.lines[1].rec_name, + '05/01/2022|Rev|23.50 usd|Initial [Dividend]|3.0000 u') + self.assertEqual( + book_cash.lines[0].rec_name, + '05/02/2022|Exp/Sp|-33.00 usd|buy shares, fee [-]') + + self.assertEqual(book_asset.lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual(book_asset.lines[0].asset_dividend, Decimal('0.0')) + self.assertEqual(book_asset.lines[0].trade_fee, Decimal('3.0')) + + @with_transaction() + def test_yield_fee_transfer_from_splitbooking_asset_out(self): + """ check out-booking, transfer from asset --> cash, + fee on counterpart of splitbooking + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual(len(book_asset.lines), 1) + self.assertEqual(book_cash.rec_name, 'Cash | 0.00 usd | Open') + self.assertEqual(len(book_cash.lines), 0) + + Cashbook.write(*[ + [book_asset], + { + 'lines': [('create', [{ + 'bookingtype': 'spout', + 'date': date(2022, 5, 2), + 'description': 'sell shares, fee', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_cash.id, + 'description': 'sell shares', + 'quantity': Decimal('1.0'), + 'amount': Decimal('20.0'), + }, { + 'splittype': 'cat', + 'category': as_cfg.fee_category.id, + 'description': 'trade fee', + 'amount': Decimal('3.0'), + 'quantity': Decimal('0.0'), + }])], + }])], + }]) + + Line.wfcheck(book_asset.lines) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 0.50 usd | Open | 2.0000 u') + self.assertEqual(len(book_asset.lines), 2) + self.assertEqual(book_cash.rec_name, 'Cash | 20.00 usd | Open') + self.assertEqual(len(book_cash.lines), 1) + + self.assertEqual( + book_asset.lines[0].rec_name, + '05/01/2022|Rev|23.50 usd|Initial [Dividend]|3.0000 u') + self.assertEqual( + book_asset.lines[1].rec_name, + '05/02/2022|Exp/Sp|-23.00 usd|sell shares, fee [-]|-1.0000 u') + self.assertEqual( + book_cash.lines[0].rec_name, + '05/02/2022|from|20.00 usd|sell shares [Depot | ' + + '0.50 usd | Open | 2.0000 u]') + + self.assertEqual(book_asset.lines[1].asset_gainloss, Decimal('0.0')) + self.assertEqual(book_asset.lines[1].asset_dividend, Decimal('0.0')) + self.assertEqual(book_asset.lines[1].trade_fee, Decimal('3.0')) + + @with_transaction() + def test_yield_dividend_category_in(self): + """ check out-booking, dividend in/out + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + self.prep_type('Cash', 'C') + category_in = self.prep_category(name='Income', cattype='in') + 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_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 2), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': category_in.id, + 'description': 'Initial', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + lines = Line.create([{ + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'in', + 'description': 'dividend', + 'category': as_cfg.dividend_category.id, + 'amount': Decimal('4.0'), + 'quantity': Decimal('0.0'), + }]) + + self.assertEqual(len(lines), 1) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Rev|4.00 usd|dividend [Dividend]|0.0000 u') + + self.assertEqual(lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual(lines[0].asset_dividend, Decimal('4.0')) + self.assertEqual(lines[0].trade_fee, Decimal('0.0')) + self.assertEqual( + book_asset.rec_name, + 'Depot | 27.50 usd | Open | 3.0000 u') + + @with_transaction() + def test_yield_dividend_category_out(self): + """ check out-booking, dividend in/out + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Category = pool.get('cashbook.category') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + self.prep_type('Cash', 'C') + category_in = self.prep_category(name='Income', cattype='in') + 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_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 2), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': category_in.id, + 'description': 'Initial', + }, ])], + }]) + + Category.write(*[ + [as_cfg.dividend_category], + { + 'cattype': 'out', + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + lines = Line.create([{ + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'out', + 'description': 'dividend minus', + 'category': as_cfg.dividend_category.id, + 'amount': Decimal('4.0'), + 'quantity': Decimal('0.0'), + }]) + + self.assertEqual(len(lines), 1) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp|-4.00 usd|dividend minus [Dividend]|0.0000 u') + + self.assertEqual(lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual(lines[0].asset_dividend, Decimal('-4.0')) + self.assertEqual(lines[0].trade_fee, Decimal('0.0')) + self.assertEqual( + book_asset.rec_name, + 'Depot | 19.50 usd | Open | 3.0000 u') + + @with_transaction() + def test_yield_dividend_transfer_from_splitbooking_cash_out(self): + """ check out-booking, transfer from cash --> asset, + dividend on counterpart of splitbooking + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_in = self.prep_category(name='Income', cattype='in') + + book_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': category_in.id, + 'description': 'Initial', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual(len(book_asset.lines), 1) + self.assertEqual(book_cash.rec_name, 'Cash | 0.00 usd | Open') + self.assertEqual(len(book_cash.lines), 0) + + Cashbook.write(*[ + [book_cash], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 2), + 'description': 'buy shares, dividend', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'sell shares', + 'quantity': Decimal('1.0'), + 'amount': Decimal('10.0'), + }, { + 'splittype': 'cat', + 'category': as_cfg.dividend_category.id, + 'description': 'dividend', + 'amount': Decimal('10.0'), + }])], + }])], + }]) + + Line.wfcheck(book_cash.lines) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 13.50 usd | Open | 2.0000 u') + self.assertEqual(len(book_asset.lines), 2) + self.assertEqual(book_cash.rec_name, 'Cash | 20.00 usd | Open') + self.assertEqual(len(book_cash.lines), 1) + + self.assertEqual( + book_asset.lines[0].rec_name, + '05/02/2022|to|-10.00 usd|sell shares [Cash | ' + + '20.00 usd | Open]|-1.0000 u') + self.assertEqual( + book_asset.lines[1].rec_name, + '05/01/2022|Rev|23.50 usd|Initial [Income]|3.0000 u') + self.assertEqual( + book_cash.lines[0].rec_name, + '05/02/2022|Rev/Sp|20.00 usd|buy shares, dividend [-]') + + self.assertEqual(book_asset.lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual( + book_asset.lines[0].asset_dividend, Decimal('10.0')) + self.assertEqual(book_asset.lines[0].trade_fee, Decimal('0.0')) + + @with_transaction() + def test_yield_dividend_transfer_to_splitbooking_aset(self): + """ check out-booking, transfer from asset --> cash, + dividend on counterpart of splitbooking + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_in = self.prep_category(name='Income', cattype='in') + + book_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': category_in.id, + 'description': 'Initial', + }, ])], + }]) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual(len(book_asset.lines), 1) + self.assertEqual(book_cash.rec_name, 'Cash | 0.00 usd | Open') + self.assertEqual(len(book_cash.lines), 0) + + Cashbook.write(*[ + [book_asset], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 2), + 'description': 'buy shares, dividend', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_cash.id, + 'description': 'buy shares', + 'quantity': Decimal('1.0'), + 'amount': Decimal('10.0'), + }, { + 'splittype': 'cat', + 'category': as_cfg.dividend_category.id, + 'description': 'dividend', + 'amount': Decimal('10.0'), + 'quantity': Decimal('0.0'), + }])], + }])], + }]) + + Line.wfcheck(book_asset.lines) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 43.50 usd | Open | 4.0000 u') + self.assertEqual(len(book_asset.lines), 2) + self.assertEqual(book_cash.rec_name, 'Cash | -10.00 usd | Open') + self.assertEqual(len(book_cash.lines), 1) + + self.assertEqual( + book_asset.lines[0].rec_name, + '05/01/2022|Rev|23.50 usd|Initial [Income]|3.0000 u') + self.assertEqual( + book_asset.lines[1].rec_name, + '05/02/2022|Rev/Sp|20.00 usd|buy shares, dividend [-]|1.0000 u') + self.assertEqual( + book_cash.lines[0].rec_name, + '05/02/2022|to|-10.00 usd|buy shares [Depot | ' + + '43.50 usd | Open | 4.0000 u]') + + self.assertEqual(book_asset.lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual( + book_asset.lines[0].asset_dividend, + Decimal('00.0')) + self.assertEqual(book_asset.lines[0].trade_fee, Decimal('0.0')) + + self.assertEqual(book_asset.lines[1].asset_gainloss, Decimal('0.0')) + self.assertEqual( + book_asset.lines[1].asset_dividend, + Decimal('10.0')) + self.assertEqual(book_asset.lines[1].trade_fee, Decimal('0.0')) + + @with_transaction() + def test_yield_gainloss_spout(self): + """ check out-booking, split with fee and profit (1) + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (1)', + }, ])], + }]) + + # add counter-account for profit or loss of + # depot-account + book_gainloss = as_cfg.gainloss_book + # sale all shares with profit and fee + # buy: 23.50 + # sale: 32.90 (+40%) + # fee: 2.50 + # asset (buy amount): 23.50 + # booking: asset -> cash: - 30.40 + # asset -> (category) fee: - 2.50 + # asset <- gain-loss: 9.40 + # ------- + # 0.00 + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual( + book_gainloss.rec_name, + 'Profit-Loss | 0.00 usd | Open') + lines = Line.create([{ + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'spout', + 'description': 'all out (1)', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_cash.id, + 'description': 'sale with 40% profit (1)', + 'quantity': Decimal('3.0'), + 'amount': Decimal('30.4'), + }, { + 'splittype': 'cat', + 'description': 'trade fee (1)', + 'category': as_cfg.fee_category.id, + 'amount': Decimal('2.5'), + 'quantity': Decimal('0.0'), + }, { + 'splittype': 'tr', + 'booktransf': book_gainloss.id, + 'description': 'profit of sale (1)', + 'amount': Decimal('-9.4'), + 'quantity': Decimal('0.0'), + }])], + }]) + + self.assertEqual(len(lines), 1) + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u') + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u') + self.assertEqual(len(lines[0].splitlines), 3) + self.assertEqual(lines[0].reference, None) + self.assertEqual(len(lines[0].references), 2) + self.assertEqual( + lines[0].references[0].rec_name, + '05/02/2022|from|30.40 usd|sale with 40% profit (1) ' + + '[Depot | 0.00 usd | Open | 0.0000 u]') + self.assertEqual( + lines[0].references[1].rec_name, + '05/02/2022|from|-9.40 usd|profit of sale (1) ' + + '[Depot | 0.00 usd | Open | 0.0000 u]') + + self.assertEqual(lines[0].asset_gainloss, Decimal('9.4')) + self.assertEqual(lines[0].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[0].trade_fee, Decimal('2.5')) + + self.assertEqual( + book_asset.rec_name, + 'Depot | 0.00 usd | Open | 0.0000 u') + # negative amount on profit/loss-account means success + self.assertEqual( + book_gainloss.rec_name, + 'Profit-Loss | -9.40 usd | Open') + + # check searcher + lines = Line.search([('asset_gainloss', '=', Decimal('9.4'))]) + self.assertEqual(len(lines), 1) + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp/Sp|-23.50 usd|all out (1) [-]|-3.0000 u') + + @with_transaction() + def test_yield_gainloss_spin(self): + """ check in-booking, split with profit (2) + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (2)', + }, ])], + }]) + + # add counter-account for profit or loss of + # depot-account + book_gainloss = as_cfg.gainloss_book + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual( + book_gainloss.rec_name, + 'Profit-Loss | 0.00 usd | Open') + lines = Line.create([{ + 'cashbook': book_cash.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'spin', + 'description': 'all out (2)', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'sale with 40% profit (2)', + 'quantity': Decimal('3.0'), + 'amount': Decimal('30.4'), + }, ])], + }, { + 'cashbook': book_gainloss.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'spin', + 'description': 'profit (2)', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'profit of sale (2)', + 'amount': Decimal('-9.4'), + 'quantity': Decimal('0.0'), + }])], + }]) + + self.assertEqual(len(lines), 2) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Rev/Sp|30.40 usd|all out (2) [-]') + self.assertEqual(len(lines[0].splitlines), 1) + self.assertEqual(lines[0].reference, None) + self.assertEqual(len(lines[0].references), 1) + self.assertEqual( + lines[0].references[0].rec_name, + '05/02/2022|to|-30.40 usd|sale with 40% profit (2) ' + + '[Cash | 30.40 usd | Open]|-3.0000 u') + + self.assertEqual(lines[0].asset_gainloss, None) + self.assertEqual(lines[0].asset_dividend, None) + self.assertEqual(lines[0].trade_fee, None) + self.assertEqual( + lines[0].references[0].asset_gainloss, Decimal('0.0')) + self.assertEqual( + lines[0].references[0].asset_dividend, Decimal('0.0')) + self.assertEqual( + lines[0].references[0].trade_fee, Decimal('0.0')) + + self.assertEqual( + lines[1].rec_name, + '05/02/2022|Rev/Sp|-9.40 usd|profit (2) [-]') + self.assertEqual(len(lines[1].splitlines), 1) + self.assertEqual(lines[1].reference, None) + self.assertEqual(len(lines[1].references), 1) + self.assertEqual( + lines[1].references[0].rec_name, + '05/02/2022|to|9.40 usd|profit of sale (2) [Profit-Loss' + + ' | -9.40 usd | Open]|0.0000 u') + + self.assertEqual(lines[1].asset_gainloss, None) + self.assertEqual(lines[1].asset_dividend, None) + self.assertEqual(lines[1].trade_fee, None) + self.assertEqual( + lines[1].references[0].asset_gainloss, Decimal('9.4')) + self.assertEqual( + lines[1].references[0].asset_dividend, Decimal('0.0')) + self.assertEqual( + lines[1].references[0].trade_fee, Decimal('0.0')) + + self.assertEqual( + book_asset.rec_name, 'Depot | 2.50 usd | Open | 0.0000 u') + # negative amount on profit/loss-account means success + self.assertEqual( + book_gainloss.rec_name, 'Profit-Loss | -9.40 usd | Open') + + # check searcher + lines = Line.search([('asset_gainloss', '=', Decimal('9.4'))]) + self.assertEqual(len(lines), 1) + self.assertEqual( + lines[0].rec_name, + '05/02/2022|to|9.40 usd|profit of sale (2) [Profit-Loss' + + ' | -9.40 usd | Open]|0.0000 u') + + @with_transaction() + def test_yield_gainloss_spin2(self): + """ check in-booking, split with profit (5) + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (2)', + }, ])], + }]) + + # add counter-account for profit or loss of + # depot-account + book_gainloss = as_cfg.gainloss_book + + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual( + book_gainloss.rec_name, + 'Profit-Loss | 0.00 usd | Open') + lines = Line.create([{ + 'cashbook': book_cash.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'spout', + 'description': 'guv (5)', + 'splitlines': [('create', [{ + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'profit/loss (5)', + 'quantity': Decimal('-3.0'), + 'amount': Decimal('-23.5'), + }, { + 'splittype': 'tr', + 'booktransf': book_gainloss.id, + 'description': 'profit/loss (5)', + 'amount': Decimal('-9.4'), + }, ])], + }]) + + self.assertEqual(len(lines), 1) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp/Sp|32.90 usd|guv (5) [-]') + self.assertEqual(len(lines[0].splitlines), 2) + self.assertEqual( + lines[0].splitlines[0].rec_name, + 'Exp/Sp|-23.50 usd|profit/loss (5) [Depot | 0.00 usd' + + ' | Open | 0.0000 u]') + self.assertEqual( + lines[0].splitlines[1].rec_name, + 'Exp/Sp|-9.40 usd|profit/loss (5) [Profit-Loss | ' + + '-9.40 usd | Open]') + + self.assertEqual(lines[0].reference, None) + self.assertEqual(len(lines[0].references), 2) + self.assertEqual( + lines[0].references[0].rec_name, + '05/02/2022|from|-23.50 usd|profit/loss (5) [Cash | ' + + '32.90 usd | Open]|-3.0000 u') + self.assertEqual( + lines[0].references[1].rec_name, + '05/02/2022|from|-9.40 usd|profit/loss (5) [Cash | ' + + '32.90 usd | Open]') + + self.assertEqual(lines[0].asset_gainloss, None) + self.assertEqual(lines[0].asset_dividend, None) + self.assertEqual(lines[0].trade_fee, None) + self.assertEqual( + lines[0].references[0].asset_gainloss, Decimal('9.4')) + self.assertEqual( + lines[0].references[0].asset_dividend, Decimal('0.0')) + self.assertEqual( + lines[0].references[0].trade_fee, Decimal('0.0')) + + self.assertEqual( + book_asset.rec_name, 'Depot | 0.00 usd | Open | 0.0000 u') + self.assertEqual( + book_gainloss.rec_name, 'Profit-Loss | -9.40 usd | Open') + + # check searcher + lines = Line.search([('asset_gainloss', '=', Decimal('9.4'))]) + self.assertEqual(len(lines), 1) + self.assertEqual( + lines[0].rec_name, + '05/02/2022|from|-23.50 usd|profit/loss (5) [Cash | ' + + '32.90 usd | Open]|-3.0000 u') + + @with_transaction() + def test_yield_gainloss_mvout(self): + """ check out-booking, transfer with profit-account, fee + 2x transfer, 1x category, 3x transfers (3) + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (3)', + }, ])], + }]) + + # add counter-account for profit or loss of + # depot-account + book_gainloss = as_cfg.gainloss_book + # sale all shares with profit and fee + # buy: 23.50 + # sale: 32.90 (+40%) + # fee: 2.50 + # asset (buy amount): 23.50 + # booking: asset -> cash: - 30.40 + # asset -> (category) fee: - 2.50 + # asset <- gain-loss: 9.40 + # ------- + # 0.00 + self.assertEqual( + book_asset.rec_name, + 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual( + book_gainloss.rec_name, + 'Profit-Loss | 0.00 usd | Open') + lines = Line.create([{ + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'mvout', + 'booktransf': book_cash.id, + 'description': 'sale with 40% profit (3)', + 'quantity': Decimal('3.0'), + 'amount': Decimal('30.4'), + }, { + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'out', + 'description': 'trade fee (3)', + 'category': as_cfg.fee_category.id, + 'quantity': Decimal('0.0'), + 'amount': Decimal('2.5'), + }, { + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'mvin', + 'description': 'profit of sale (3)', + 'booktransf': book_gainloss.id, + 'quantity': Decimal('0.0'), + 'amount': Decimal('9.4'), + }]) + + self.assertEqual(len(lines), 3) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|to|-30.40 usd|sale with 40% profit (3) ' + + '[Cash | 30.40 usd | Open]|-3.0000 u') + self.assertEqual(lines[0].asset_gainloss, Decimal('0.0')) + self.assertEqual(lines[0].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[0].trade_fee, Decimal('0.0')) + + self.assertEqual( + lines[1].rec_name, + '05/02/2022|Exp|-2.50 usd|trade fee (3) [Fee]|0.0000 u') + self.assertEqual(lines[1].asset_gainloss, Decimal('0.0')) + self.assertEqual(lines[1].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[1].trade_fee, Decimal('2.5')) + + self.assertEqual( + lines[2].rec_name, + '05/02/2022|from|9.40 usd|profit of sale (3) ' + + '[Profit-Loss | -9.40 usd | Open]|0.0000 u') + self.assertEqual(lines[2].asset_gainloss, Decimal('9.4')) + self.assertEqual(lines[2].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[2].trade_fee, Decimal('0.0')) + + self.assertEqual( + book_asset.rec_name, 'Depot | 0.00 usd | Open | 0.0000 u') + self.assertEqual( + book_gainloss.rec_name, 'Profit-Loss | -9.40 usd | Open') + self.assertEqual( + book_cash.rec_name, 'Cash | 30.40 usd | Open') + + # check searcher + lines = Line.search([('asset_gainloss', '=', Decimal('9.4'))]) + self.assertEqual(len(lines), 1) + + @with_transaction() + def test_yield_gainloss_mv_sp(self): + """ check out-booking, transfer with profit-account, fee + 2x transfer, 1x category, 1x transfer to splitbooking (4) + """ + pool = Pool() + Cashbook = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Line = pool.get('cashbook.line') + + company = self.prep_company() + with Transaction().set_context({'company': company.id}): + as_cfg = self.prep_yield_config( + 'Fee', 'Dividend', 'Profit-Loss', company) + + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + 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_cash, = Cashbook.create([{ + 'name': 'Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book_asset, = Cashbook.create([{ + 'name': 'Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('23.50'), + 'quantity': Decimal('3.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'Initial (4)', + }, ])], + }]) + + # add counter-account for profit or loss of + # depot-account + book_gainloss = as_cfg.gainloss_book + # sale all shares with profit and fee + # buy: 23.50 + # sale: 32.90 (+40%) + # fee: 2.50 + # asset (buy amount): 23.50 + # booking: asset -> cash: - 30.40 + # asset -> (category) fee: - 2.50 + # asset <- gain-loss: 9.40 + # ------- + # 0.00 + self.assertEqual( + book_asset.rec_name, 'Depot | 23.50 usd | Open | 3.0000 u') + self.assertEqual( + book_gainloss.rec_name, 'Profit-Loss | 0.00 usd | Open') + lines = Line.create([{ + 'cashbook': book_cash.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'spout', # negate transaction, because the + 'description': 'all out (4)', # category 'as_cfg.fee_category' + 'splitlines': [('create', [{ # is for out-only + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'description': 'sale with 40% profit (4)', + 'quantity': Decimal('-3.0'), + 'amount': Decimal('-32.9'), # profit + fee + }, { + 'splittype': 'cat', + 'category': as_cfg.fee_category.id, + 'description': 'trade fee (4)', + 'amount': Decimal('2.5'), # fee + }])], + }, { + 'cashbook': book_asset.id, + 'date': date(2022, 5, 2), + 'bookingtype': 'mvin', + 'booktransf': book_gainloss.id, + 'amount': Decimal('9.4'), + 'quantity': Decimal('0.0'), + 'description': 'gainloss_mv_sp (4)', + }]) + + self.assertEqual(len(lines), 2) + Line.wfcheck(lines) + + self.assertEqual( + lines[0].rec_name, + '05/02/2022|Exp/Sp|30.40 usd|all out (4) [-]') + # non-asset cashbook + self.assertEqual(lines[0].asset_gainloss, None) + self.assertEqual(lines[0].asset_dividend, None) + self.assertEqual(lines[0].trade_fee, None) + self.assertEqual(lines[0].reference, None) + self.assertEqual(len(lines[0].references), 1) + self.assertEqual( + lines[0].references[0].rec_name, + '05/02/2022|from|-32.90 usd|sale with 40% profit (4) ' + + '[Cash | 30.40 usd | Open]|-3.0000 u') + self.assertEqual( + lines[0].references[0].asset_gainloss, Decimal('0.0')) + self.assertEqual( + lines[0].references[0].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[0].references[0].trade_fee, Decimal('2.5')) + + self.assertEqual( + lines[1].rec_name, + '05/02/2022|from|9.40 usd|gainloss_mv_sp (4) [Profit-Loss' + + ' | -9.40 usd | Open]|0.0000 u') + self.assertEqual(lines[1].asset_gainloss, Decimal('9.4')) + self.assertEqual(lines[1].asset_dividend, Decimal('0.0')) + self.assertEqual(lines[1].trade_fee, Decimal('0.0')) + self.assertEqual(lines[1].reference, None) + self.assertEqual(len(lines[1].references), 1) + self.assertEqual( + lines[1].references[0].rec_name, + '05/02/2022|to|-9.40 usd|gainloss_mv_sp (4) [Depot | ' + + '0.00 usd | Open | 0.0000 u]') + + self.assertEqual( + book_asset.rec_name, 'Depot | 0.00 usd | Open | 0.0000 u') + self.assertEqual( + book_gainloss.rec_name, 'Profit-Loss | -9.40 usd | Open') + self.assertEqual( + book_cash.rec_name, 'Cash | 30.40 usd | Open') + + # check searcher + lines = Line.search([('asset_gainloss', '=', Decimal('9.4'))]) + self.assertEqual(len(lines), 1) + +# end YieldTestCase diff --git a/tryton.cfg b/tryton.cfg index 937fdf1..cf6fa71 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -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 diff --git a/versiondep.txt b/versiondep.txt index ed656a2..321a202 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -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 diff --git a/view/assetconf_form.xml b/view/assetconf_form.xml new file mode 100644 index 0000000..c3b563a --- /dev/null +++ b/view/assetconf_form.xml @@ -0,0 +1,14 @@ + + +
+ +