From 269e744b8f6dbcbae9759de5f383d0bd63ad36e3 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 21 Jan 2023 19:18:36 +0100 Subject: [PATCH 01/11] Version 6.0.1 --- README.rst | 4 ++++ tryton.cfg | 2 +- versiondep.txt | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 67199fe..955ff60 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,10 @@ Requires Changes ======= +*6.0.1 - 21.01.2023* + +- works + *6.0.0 - 20.12.2022* - init diff --git a/tryton.cfg b/tryton.cfg index 937fdf1..e5df578 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.0 +version=6.0.1 depends: cashbook investment diff --git a/versiondep.txt b/versiondep.txt index ed656a2..22e9ccd 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -1 +1,2 @@ -cashbook;6.0.21;6.0.999;mds +cashbook;6.0.22;6.0.999;mds +investment;6.0.23;6.0.999;mds From bbf94c95dfed3bde28fce7cdc14cfa6e61cd0328 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 22 Jan 2023 10:28:09 +0100 Subject: [PATCH 03/11] line: fix rec_name --- line.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/line.py b/line.py index f9a4487..27eae20 100644 --- a/line.py +++ b/line.py @@ -95,7 +95,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): recname += '|%(quantity)s %(uom_symbol)s' % { 'quantity': Report.format_number(credit - debit, lang=None, digits=self.quantity_digits), - 'uom_symbol': self.quantity_uom.symbol, + 'uom_symbol': getattr(self.quantity_uom, 'symbol', '-'), } return recname From 12e088f79a70377fbf6519bd80ec74b3da53a324 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 23 Jan 2023 22:29:41 +0100 Subject: [PATCH 04/11] line: fix transfer between cash-/asset-book with zero quantity --- line.py | 17 ++-- tests/test_book.py | 206 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 212 insertions(+), 11 deletions(-) diff --git a/line.py b/line.py index 27eae20..c35946a 100644 --- a/line.py +++ b/line.py @@ -181,19 +181,16 @@ class Line(SecondUomMixin, metaclass=PoolMeta): 'quantity_2nd_uom': splitline.quantity \ if (asset_books == 2) and (diff_uom == True) else None, }) - elif booktransf_uom is None: - # counterpart-cashbook has no uom -> no quantity + elif sum([1 if booktransf_uom is not None else 0, + 1 if line_uom is not None else 0]) == 1: + # one of the related cashbooks only is asset-type result.update({ - 'quantity': None, + 'quantity': line.quantity, 'quantity_2nd_uom': None, }) - else : - if line_uom is None: - result.update({ - 'quantity': line.quantity, - 'quantity_2nd_uom': None, - }) - elif line_uom == booktransf_uom: + elif sum([1 if booktransf_uom is not None else 0, + 1 if line_uom is not None else 0]) == 2: + if line_uom == booktransf_uom: result.update({ 'quantity': line.quantity, 'quantity_2nd_uom': None, diff --git a/tests/test_book.py b/tests/test_book.py index 463b2d0..dc69b01 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -677,6 +677,87 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): book.lines = l1 book.lines[-1].on_change_quantity() + @with_transaction() + def test_assetbook_check_mvout_zero_quantity(self): + """ create cashbook + line, bookingtype 'mvout' + transfer from asset-book to cash-book - zero quantity, + to book gain/loss + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Category = pool.get('cashbook.category') + BType = pool.get('cashbook.type') + + type_cash = self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + category_out = self.prep_category(name='Out Category', cattype='out') + company = self.prep_company() + party = self.prep_party() + + asset = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + book1, = Book.create([{ + 'name': 'Asset-Book', + 'btype': type_depot.id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book2, = Book.create([{ + 'name': 'Cash-Book', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + Book.write(*[ + [book1], + { + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'loss at sell', + 'category': category_out.id, + 'bookingtype': 'mvout', + 'amount': Decimal('10.0'), + 'booktransf': book2.id, + 'quantity': Decimal('0.0'), + }])], + }, + ]) + self.assertEqual(book1.rec_name, 'Asset-Book | -10.00 usd | Open | 0.0000 u') + self.assertEqual(len(book1.lines), 1) + self.assertEqual(book1.lines[0].rec_name, + '05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | 0.00 usd | Open]|0.0000 u') + self.assertEqual(book2.rec_name, 'Cash-Book | 0.00 usd | Open') + self.assertEqual(len(book2.lines), 0) + + Line.wfcheck(list(book1.lines)) + + self.assertEqual(book1.rec_name, 'Asset-Book | -10.00 usd | Open | 0.0000 u') + self.assertEqual(len(book1.lines), 1) + self.assertEqual(book1.lines[0].rec_name, + '05/01/2022|to|-10.00 usd|loss at sell [Cash-Book | 10.00 usd | Open]|0.0000 u') + self.assertEqual(book2.rec_name, 'Cash-Book | 10.00 usd | Open') + self.assertEqual(len(book2.lines), 1) + self.assertEqual(book2.lines[0].rec_name, + '05/01/2022|from|10.00 usd|loss at sell [Asset-Book | -10.00 usd | Open | 0.0000 u]') + @with_transaction() def test_assetbook_check_mvin(self): """ create cashbook + line, bookingtype 'mvin' @@ -1401,6 +1482,129 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): self.assertEqual(books[1].lines[0].rec_name, '05/01/2022|to|-6.00 usd|from cashbook [Book 1 | 11.00 usd | Open | 4.00 u]|-2.50 u') + @with_transaction() + def test_assetbook_split_in_category_and_assetbook_zero_quantity(self): + """ splitbooking incoming to asset-cahbook, + from category and asset-cashbook, zero qunatity to book + gain/loss of a sell from asset-cashbook + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + Uom = pool.get('product.uom') + + types = self.prep_type() + BType.write(*[ + [types], + { + 'feature': 'asset', + }]) + category = self.prep_category(cattype='in') + + company = self.prep_company() + party = self.prep_party() + asset = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'start_date': date(2022, 4, 1), + 'name': 'Book 1', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }, { + 'start_date': date(2022, 4, 1), + 'name': 'Book 2', + 'btype': types.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 2, + }]) + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'splitlines': [('create', [{ + 'amount': Decimal('5.0'), + 'splittype': 'cat', + 'description': 'gain on sell', + 'category': category.id, + 'quantity': Decimal('0.0'), + }, { + 'amount': Decimal('6.0'), + 'splittype': 'tr', + 'description': 'transfer zero quantity', + 'booktransf': books[1].id, + 'quantity': Decimal('0.0'), + }])], + }])], + }]) + + self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 0.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual(books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual(books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | 0.00 usd | Open | 0.00 u]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(len(books[0].lines[0].references), 0) + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('0.0')) + self.assertEqual(len(books[1].lines), 0) + + Line.wfcheck([books[0].lines[0]]) + + self.assertEqual(books[0].rec_name, 'Book 1 | 11.00 usd | Open | 0.00 u') + self.assertEqual(books[0].balance_all, Decimal('11.0')) + self.assertEqual(len(books[0].lines), 1) + + self.assertEqual(books[0].lines[0].rec_name, '05/01/2022|Rev/Sp|11.00 usd|- [-]|0.00 u') + self.assertEqual(books[0].lines[0].splitline_has_quantity, True) + + self.assertEqual(len(books[0].lines[0].splitlines), 2) + self.assertEqual(books[0].lines[0].splitlines[0].rec_name, + 'Rev/Sp|5.00 usd|gain on sell [Cat1]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[0].booktransf, None) + self.assertEqual(books[0].lines[0].splitlines[1].rec_name, + 'Rev/Sp|6.00 usd|transfer zero quantity [Book 2 | -6.00 usd | Open | 0.00 u]|0.00 u') + self.assertEqual(books[0].lines[0].splitlines[1].booktransf.rec_name, + 'Book 2 | -6.00 usd | Open | 0.00 u') + self.assertEqual(len(books[0].lines[0].references), 1) + self.assertEqual(books[0].lines[0].references[0].rec_name, + '05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | 11.00 usd | Open | 0.00 u]|0.00 u') + self.assertEqual(books[0].lines[0].reference, None) + + self.assertEqual(books[1].rec_name, 'Book 2 | -6.00 usd | Open | 0.00 u') + self.assertEqual(books[1].balance_all, Decimal('-6.0')) + self.assertEqual(len(books[1].lines), 1) + self.assertEqual(books[1].lines[0].rec_name, + '05/01/2022|to|-6.00 usd|transfer zero quantity [Book 1 | 11.00 usd | Open | 0.00 u]|0.00 u') + @with_transaction() def test_assetbook_split_in_catergory_asset_diff_unit(self): """ splitbooking incoming to asset-cahbook, @@ -1744,7 +1948,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): @with_transaction() def test_assetbook_split_out_category_and_assetbook(self): """ splitbooking outgoing, - from asset-cashbook to asset-cahbook and to category + from asset-cashbook to asset-cashbook and to category """ pool = Pool() Book = pool.get('cashbook.book') From 674f72476e160bee2077c14b5df915d80da3f19f Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Tue, 24 Jan 2023 21:25:29 +0100 Subject: [PATCH 05/11] book: asset-query ausgelagert --- book.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/book.py b/book.py index 332d514..2858b37 100644 --- a/book.py +++ b/book.py @@ -152,16 +152,15 @@ class Book(SymbolMixin, metaclass=PoolMeta): return 4 @classmethod - def get_asset_quantity(cls, cashbooks, names): - """ get quantities - """ + def get_asset_quantity_sql(cls): + """ get table of asset and its value, rate, ... + """ pool = Pool() CBook = pool.get('cashbook.book') BookType = pool.get('cashbook.type') Line = pool.get('cashbook.line') Asset = pool.get('investment.asset') Currency = pool.get('currency.currency') - Uom = pool.get('product.uom') tab_book = CBook.__table__() tab_type = BookType.__table__() tab_line = Line.__table__() @@ -169,13 +168,9 @@ 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() 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, @@ -209,9 +204,27 @@ class Book(SymbolMixin, metaclass=PoolMeta): tab_book.currency, tab_cur.digits, tab_asset.uom, tab_book.quantity_uom, tab_asset.currency, tab_balance.balance], - where=tab_book.id.in_([x.id for x in cashbooks]) & \ - (tab_type.feature == 'asset'), + where=(tab_type.feature == 'asset'), ) + return (query, tab_book) + + @classmethod + def get_asset_quantity(cls, cashbooks, names): + """ get quantities + """ + pool = Pool() + CBook = pool.get('cashbook.book') + Uom = pool.get('product.uom') + Currency = pool.get('currency.currency') + cursor = Transaction().connection.cursor() + context = Transaction().context + (query, tab_book) = cls.get_asset_quantity_sql() + + result = {x:{y.id: None for y in cashbooks} for x in names} + company_currency = CBook.default_currency() + + query.where &= tab_book.id.in_([x.id for x in cashbooks]) & \ + (tab_book.btype != None) cursor.execute(*query) records = cursor.fetchall() From a315f4e768134a73beb00f2b91eb3549605503f9 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 28 Jan 2023 13:03:24 +0100 Subject: [PATCH 06/11] cashbook: view current_value/-ref on chasbooks with btype=None if asset-cashbooks are below --- book.py | 167 ++++++++++++++++++++++++++++++++++----------- tests/test_book.py | 164 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 293 insertions(+), 38 deletions(-) diff --git a/book.py b/book.py index 2858b37..83c8f22 100644 --- a/book.py +++ b/book.py @@ -81,18 +81,18 @@ class Book(SymbolMixin, metaclass=PoolMeta): help='Valuation of the investment based on the current stock market price.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), + 'invisible': Eval('show_performance', False) == False, + }, depends=['currency_digits', 'show_performance']), 'get_asset_quantity') current_value_ref = fields.Function(fields.Numeric(string='Value (Ref.)', help='Valuation of the investment based on the current stock exchange price, converted into the company currency.', readonly=True, digits=(16, Eval('currency_digits', 2)), states={ 'invisible': Or( - Eval('feature', '') != 'asset', + Eval('show_performance', False) == False, ~Bool(Eval('company_currency', -1)), ), - }, depends=['currency_digits', 'feature', 'company_currency']), + }, depends=['currency_digits', 'show_performance', 'company_currency']), 'get_asset_quantity') # performance @@ -100,14 +100,16 @@ class Book(SymbolMixin, metaclass=PoolMeta): help='Difference between acquisition value and current value', readonly=True, digits=(16, Eval('currency_digits', 2)), states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), 'get_asset_quantity') + 'invisible': Eval('show_performance', False) == False, + }, depends=['currency_digits', 'show_performance']), 'get_asset_quantity') diff_percent = fields.Function(fields.Numeric(string='Percent', help='percentage performance since acquisition', readonly=True, digits=(16, Eval('currency_digits', 2)), states={ - 'invisible': Eval('feature', '') != 'asset', - }, depends=['currency_digits', 'feature']), 'get_asset_quantity') + 'invisible': Eval('show_performance', False) == False, + }, depends=['currency_digits', 'show_performance']), 'get_asset_quantity') + show_performance = fields.Function(fields.Boolean(string='Performance', + readonly=True), 'on_change_with_show_performance') current_rate = fields.Function(fields.Numeric(string='Rate', help='Rate per unit of investment based on current stock exchange price.', readonly=True, digits=(16, Eval('currency_digits', 2)), @@ -119,7 +121,7 @@ class Book(SymbolMixin, metaclass=PoolMeta): def view_attributes(cls): return super(Book, cls).view_attributes() + [ ('/tree', 'visual', - If(Eval('feature', '') == 'asset', + If(Eval('show_performance', False) == True, If(Eval('diff_percent', 0) < 0, 'danger', If(Eval('diff_percent', 0) > 0, 'success', '') ), '') @@ -220,54 +222,143 @@ class Book(SymbolMixin, metaclass=PoolMeta): context = Transaction().context (query, tab_book) = cls.get_asset_quantity_sql() - result = {x:{y.id: None for y in cashbooks} for x in names} company_currency = CBook.default_currency() + result = {x:{y.id: None for y in cashbooks} for x in names} - query.where &= tab_book.id.in_([x.id for x in cashbooks]) & \ - (tab_book.btype != None) - cursor.execute(*query) - records = cursor.fetchall() - - for record in records: + def values_from_record(rdata): + """ compute values for record + """ # uom-factor - if record[6] == record[7]: + if rdata[6] == rdata[7]: uom_factor = Decimal('1.0') else : uom_factor = Decimal( - Uom.compute_qty(Uom(record[6]), 1.0, Uom(record[7]), round=False) + Uom.compute_qty(Uom(rdata[6]), 1.0, Uom(rdata[7]), round=False) ) current_value = Currency.compute( - record[8], - record[3] * record[1] / uom_factor, - record[4] + rdata[8], + rdata[3] * rdata[1] / uom_factor, + rdata[4] ) - - values = { - 'quantity': record[1], - 'quantity_all': record[2], + return (record[0], { + 'quantity': rdata[1], + 'quantity_all': rdata[2], 'current_value': current_value, 'current_value_ref': Currency.compute( - record[8], - record[3] * record[1] / uom_factor, - company_currency if company_currency is not None else record[8], + rdata[8], + rdata[3] * rdata[1] / uom_factor, + company_currency if company_currency is not None else rdata[8], ), - 'diff_amount': current_value - record[9], + 'diff_amount': current_value - rdata[9], 'diff_percent': ( Decimal('100.0') * current_value / \ - record[9] - Decimal('100.0') - ).quantize(Decimal(str(1/10**record[5]))) \ - if record[9] != Decimal('0.0') else None, + rdata[9] - Decimal('100.0') + ).quantize(Decimal(str(1/10**rdata[5]))) \ + if rdata[9] != Decimal('0.0') else None, 'current_rate': ( - current_value / record[1] - ).quantize(Decimal(str(1/10**record[5]))) \ - if record[1] != Decimal('0.0') else None, - } + current_value / rdata[1] + ).quantize(Decimal(str(1/10**rdata[5]))) \ + if rdata[1] != Decimal('0.0') else None, + }) + + result_cache = {} + 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) + result_cache[book_id] = values + result_cache[book_id]['balance_ref'] = CBook(book_id).balance_ref + + for name in names: + 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: + # query all subordered asset-cashbooks for + # btype=None-cashbooks + query1 = [('btype.feature', '=', 'asset'), + ('parent', 'child_of', ids_nonebtypes)] + if len(result_cache.keys()) > 0: + query1.append(('id', 'not in', result_cache.keys())) + books_query = CBook.search(query1, query=True) + + # add results to cache + (query, tab_book) = cls.get_asset_quantity_sql() + query.where &= tab_book.id.in_(books_query) + cursor.execute(*query) + records = cursor.fetchall() + + for record in records: + (book_id, values) = values_from_record(record) + result_cache[book_id] = values + result_cache[book_id]['balance_ref'] = CBook(book_id).balance_ref + + # aggregate sub-cashbooks to requested cashbooks from cache + for id_none in ids_nonebtypes: + records = CBook.search([ + ('btype.feature', '=', 'asset'), + ('parent', 'child_of', [id_none]) + ]) + + values = {x:Decimal('0.0') for x in aggr_names+['balance_ref']} + for record in records: + for name in aggr_names+['balance_ref']: + values[name] += \ + result_cache.get(record.id, {}).get(name, Decimal('0.0')) + + # convert current-value-ref in company-currency to + # currency of current cashbook + cbook = CBook(id_none) + values['current_value'] = Currency.compute( + company_currency if company_currency is not None else cbook.currency, + values['current_value_ref'], + cbook.currency, + ) + + values['diff_amount'] = Currency.compute( + company_currency if company_currency is not None else cbook.currency, + values['current_value_ref'] - values['balance_ref'], + cbook.currency, + ) + + values['diff_percent'] = \ + (Decimal('100.0') * values['current_value_ref'] / \ + values['balance_ref'] - Decimal('100.0') + ).quantize( + Decimal(str(1/10**cbook.currency_digits)) + ) if values['balance_ref'] != Decimal('0.0') else None + + for name in queried_names: + result[name][id_none] = values[name] - for name in names: - result[name][record[0]] = values[name] return result + @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): """ get current cashbook to enable usage of 'symbol' diff --git a/tests/test_book.py b/tests/test_book.py index dc69b01..d3dc2c3 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -48,6 +48,168 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): self.assertEqual(book.state_string, 'Open') self.assertEqual(book.feature, 'asset') self.assertEqual(book.quantity_digits, 4) + self.assertEqual(book.show_performance, True) + + @with_transaction() + def test_assetbook_aggregated_values(self): + """ create cashbooks with hierarchy, add lines, + check values at non-type-books + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + + company = self.prep_company() + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + category_in = self.prep_category(cattype='in') + + asset = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + Asset.write(*[ + [asset], + { + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('10.0'), + }, { + 'date': date(2022, 5, 2), + 'rate': Decimal('12.5'), + }])], + }]) + self.assertEqual(asset.rec_name, 'Product 1 | 12.5000 usd/u | 05/02/2022') + + (usd, euro) = self.prep_2nd_currency(company) + self.assertEqual(company.currency.rec_name, 'Euro') + + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + with Transaction().set_context({ + 'company': company.id, + }): + books = Book.create([{ + 'name': 'L0-Euro-None', + 'btype': None, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'childs': [('create', [{ + 'name': 'L1-Euro-Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), + }])], + }, { + 'name': 'L1-USD-Cash', + 'btype': type_cash.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), # 14.29 € + }])], + }, { + 'name': 'L1-Euro-Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': euro.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), + 'quantity': Decimal('1.0'), + }])], + }, { + 'name': 'L1-USD-Depot', + 'btype': type_depot.id, + 'company': company.id, + 'currency': usd.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Cat In', + 'category': category_in.id, + 'bookingtype': 'in', + 'amount': Decimal('15.0'), # 14.29 € + 'quantity': Decimal('1.0'), + }])], + }])], + }]) + self.assertEqual(len(books), 1) + self.assertEqual(books[0].rec_name, 'L0-Euro-None') + self.assertEqual(books[0].balance, Decimal('58.57')) + self.assertEqual(books[0].balance_ref, Decimal('58.57')) + # balance of asset-books: 29,286 € + # value of asset-books: 11.9€ + 12.5USD/1.05 = 23.8€ + self.assertEqual(books[0].current_value, Decimal('23.8')) + self.assertEqual(books[0].current_value_ref, Decimal('23.8')) + self.assertEqual(books[0].diff_amount, Decimal('-5.49')) + self.assertEqual(books[0].diff_percent, Decimal('-18.74')) + + self.assertEqual(len(books[0].childs), 4) + + self.assertEqual(books[0].childs[0].rec_name, + 'L0-Euro-None/L1-Euro-Cash | 15.00 € | Open') + self.assertEqual(books[0].childs[0].current_value, None) + self.assertEqual(books[0].childs[0].current_value_ref, None) + self.assertEqual(books[0].childs[0].diff_amount, None) + self.assertEqual(books[0].childs[0].diff_percent, None) + + self.assertEqual(books[0].childs[1].rec_name, + 'L0-Euro-None/L1-Euro-Depot | 15.00 € | Open | 1.0000 u') + self.assertEqual(books[0].childs[1].asset.rec_name, + 'Product 1 | 12.5000 usd/u | 05/02/2022') + self.assertEqual(books[0].childs[1].current_value, Decimal('11.9')) + self.assertEqual(books[0].childs[1].current_value_ref, Decimal('11.9')) + self.assertEqual(books[0].childs[1].diff_amount, Decimal('-3.1')) + self.assertEqual(books[0].childs[1].diff_percent, Decimal('-20.67')) + + self.assertEqual(books[0].childs[2].rec_name, + 'L0-Euro-None/L1-USD-Cash | 15.00 usd | Open') + self.assertEqual(books[0].childs[2].current_value, None) + self.assertEqual(books[0].childs[2].current_value_ref, None) + self.assertEqual(books[0].childs[2].diff_amount, None) + self.assertEqual(books[0].childs[2].diff_percent, None) + + self.assertEqual(books[0].childs[3].rec_name, + 'L0-Euro-None/L1-USD-Depot | 15.00 usd | Open | 1.0000 u') + self.assertEqual(books[0].childs[3].asset.rec_name, + 'Product 1 | 12.5000 usd/u | 05/02/2022') + self.assertEqual(books[0].childs[3].current_value, Decimal('12.5')) + self.assertEqual(books[0].childs[3].current_value_ref, Decimal('11.9')) + self.assertEqual(books[0].childs[3].diff_amount, Decimal('-2.5')) + self.assertEqual(books[0].childs[3].diff_percent, Decimal('-16.67')) @with_transaction() def test_assetbook_create_line(self): @@ -576,6 +738,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): 'number_sequ': self.prep_sequence().id, 'start_date': date(2022, 5, 1), }]) + self.assertEqual(book2.show_performance, True) book, = Book.create([{ 'name': 'Book 1', @@ -594,6 +757,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): 'quantity': Decimal('1.5'), }])], }]) + self.assertEqual(book.show_performance, False) self.assertEqual(book.rec_name, 'Book 1 | -1.00 usd | Open') self.assertEqual(len(book.lines), 1) self.assertEqual(book.lines[0].quantity, Decimal('1.5')) From c9d6bf21a5ffa2d66ed71d5d9e300dd5dd81a249 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 28 Jan 2023 13:13:14 +0100 Subject: [PATCH 07/11] Version 6.0.2 --- README.rst | 5 +++++ tryton.cfg | 2 +- versiondep.txt | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 955ff60..c77af18 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,11 @@ Requires Changes ======= +*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 diff --git a/tryton.cfg b/tryton.cfg index e5df578..c7c153e 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.1 +version=6.0.2 depends: cashbook investment diff --git a/versiondep.txt b/versiondep.txt index 22e9ccd..e277012 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -1,2 +1,2 @@ -cashbook;6.0.22;6.0.999;mds +cashbook;6.0.23;6.0.999;mds investment;6.0.23;6.0.999;mds From 9edd2d4a95812e6ed358a84f75ecaa95d28d9a28 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 29 Jan 2023 23:16:22 +0100 Subject: [PATCH 09/11] line: add performance values to line-form --- line.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++ locale/de.po | 28 ++++++++++++++++++++++ locale/en.po | 28 ++++++++++++++++++++++ tests/test_book.py | 3 +++ view/line_form.xml | 14 +++++++++++ 5 files changed, 132 insertions(+) diff --git a/line.py b/line.py index c35946a..adfc1bd 100644 --- a/line.py +++ b/line.py @@ -85,6 +85,29 @@ class Line(SecondUomMixin, metaclass=PoolMeta): string='has quantity', readonly=True, states={'invisible': True}), 'on_change_with_splitline_has_quantity') + # performance + current_value = fields.Function(fields.Numeric(string='Value', + help='Valuation of the investment based on the current stock market price.', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states={ + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'on_change_with_current_value') + diff_amount = fields.Function(fields.Numeric(string='Difference', + help='Difference between acquisition value and current value', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states={ + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'on_change_with_diff_amount') + diff_percent = fields.Function(fields.Numeric(string='Percent', + help='percentage performance since acquisition', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states = { + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'on_change_with_diff_percent') + def get_rec_name(self, name): """ add quantities - if its a asset-cashbook """ @@ -212,6 +235,42 @@ class Line(SecondUomMixin, metaclass=PoolMeta): if cnt1 > 0: self.quantity = quantity + @fields.depends('quantity', 'cashbook', '_parent_cashbook.current_rate', 'currency_digits') + def on_change_with_current_value(self, name=None): + """ get current value of line by current stock marked price + and quantity + """ + if self.cashbook: + if (self.quantity is not None) and \ + (self.cashbook.current_rate is not None): + return ( + self.quantity * self.cashbook.current_rate + ).quantize(Decimal(str(1/10**self.currency_digits))) + + @fields.depends('quantity', 'amount', 'cashbook', '_parent_cashbook.current_rate', 'currency_digits') + def on_change_with_diff_amount(self, name=None): + """ get delta between buy and current value + """ + if self.cashbook: + if (self.quantity is not None) and \ + (self.amount is not None) and \ + (self.cashbook.current_rate is not None): + return ( + self.quantity * self.cashbook.current_rate - self.amount + ).quantize(Decimal(str(1/10**self.currency_digits))) + + @fields.depends('quantity', 'amount', 'cashbook', '_parent_cashbook.current_rate') + def on_change_with_diff_percent(self, name=None): + """ get performane percent + """ + if self.cashbook: + if (self.quantity is not None) and \ + (self.amount is not None) and (self.amount != Decimal('0.0')) and \ + (self.cashbook.current_rate is not None): + return (self.quantity * self.cashbook.current_rate * \ + Decimal('100.0') / self.amount - Decimal('100.0') + ).quantize(Decimal(str(1/10**self.currency_digits))) + @fields.depends('splitlines') def on_change_with_splitline_has_quantity(self, name=None): """ get True if splitlines are linked to asset-cashbooks diff --git a/locale/de.po b/locale/de.po index 9017a1d..cc86f22 100644 --- a/locale/de.po +++ b/locale/de.po @@ -162,6 +162,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 +222,30 @@ 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" + ################## # cashbook.recon # diff --git a/locale/en.po b/locale/en.po index 1cc9a42..ce29b4e 100644 --- a/locale/en.po +++ b/locale/en.po @@ -146,6 +146,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 +206,30 @@ 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.recon,start_quantity:" msgid "Start Quantity" msgstr "Start Quantity" diff --git a/tests/test_book.py b/tests/test_book.py index d3dc2c3..08e8f80 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -296,6 +296,9 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): self.assertEqual(book.lines[0].quantity_debit, Decimal('0.0')) self.assertEqual(book.lines[0].quantity_digits, 3) self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') + self.assertEqual(book.lines[0].current_value, Decimal('3.88')) + self.assertEqual(book.lines[0].diff_amount, Decimal('1.38')) + self.assertEqual(book.lines[0].diff_percent, Decimal('55.18')) self.assertEqual(book.lines[1].amount, Decimal('4.0')) self.assertEqual(book.lines[1].quantity, Decimal('3.3')) diff --git a/view/line_form.xml b/view/line_form.xml index 35e5369..6ecafb8 100644 --- a/view/line_form.xml +++ b/view/line_form.xml @@ -23,4 +23,18 @@ full copyright notices and license terms. --> + + + + + From 934d0fb2077f175d85b305d63a2aae48c7e5efba Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 30 Jan 2023 09:25:44 +0100 Subject: [PATCH 10/11] reconciliation: use 'quantity_digits' for digits of start/end-quantity --- reconciliation.py | 2 +- tests/test_reconciliation.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/reconciliation.py b/reconciliation.py index b7581e1..fbb190d 100644 --- a/reconciliation.py +++ b/reconciliation.py @@ -59,7 +59,7 @@ class Reconciliation(metaclass=PoolMeta): """ quantity_digits of cashbook """ if self.cashbook: - return self.cashbook.currency.digits + return self.cashbook.quantity_digits else: return 4 diff --git a/tests/test_reconciliation.py b/tests/test_reconciliation.py index f2a37c7..f4001dd 100644 --- a/tests/test_reconciliation.py +++ b/tests/test_reconciliation.py @@ -54,11 +54,11 @@ class ReconTestCase(ModuleTestCase): self.assertEqual(book.name, 'Asset-Book') self.assertEqual(book.reconciliations[0].feature, 'asset') self.assertEqual(book.reconciliations[0].rec_name, - '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u') + '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.0000 u - 0.0000 u') Reconciliation.wfcheck(list(book.reconciliations)) self.assertEqual(book.reconciliations[0].rec_name, - '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u') + '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.0000 u - 0.0000 u') @with_transaction() def test_recon_set_start_quantity_by_predecessor(self): @@ -91,6 +91,7 @@ class ReconTestCase(ModuleTestCase): 'currency': company.currency.id, 'asset': asset.id, 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, 'start_date': date(2022, 5, 1), 'number_sequ': self.prep_sequence().id, 'reconciliations': [('create', [{ @@ -119,7 +120,7 @@ class ReconTestCase(ModuleTestCase): self.assertEqual(book.name, 'Asset-Book') self.assertEqual(len(book.reconciliations), 1) self.assertEqual(book.reconciliations[0].rec_name, - '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u') + '05/01/2022 - 05/31/2022 | 0.00 usd - 0.00 usd [0] | 0.000 u - 0.000 u') self.assertEqual(len(book.reconciliations[0].lines), 0) Lines.wfcheck(list(book.lines)) @@ -134,7 +135,7 @@ class ReconTestCase(ModuleTestCase): self.assertEqual(book.reconciliations[0].state, 'check') self.assertEqual(book.reconciliations[0].rec_name, - '05/01/2022 - 05/31/2022 | 0.00 usd - 12.00 usd [2] | 0.00 u - 4.00 u') + '05/01/2022 - 05/31/2022 | 0.00 usd - 12.00 usd [2] | 0.000 u - 4.000 u') Reconciliation.wfdone(list(book.reconciliations)) self.assertEqual(book.reconciliations[0].state, 'done') @@ -144,9 +145,9 @@ class ReconTestCase(ModuleTestCase): 'date_to': date(2022, 6, 30), }]) self.assertEqual(recons[0].rec_name, - '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0] | 0.00 u - 0.00 u') + '05/31/2022 - 06/30/2022 | 0.00 usd - 0.00 usd [0] | 0.000 u - 0.000 u') Reconciliation.wfcheck(recons) self.assertEqual(recons[0].rec_name, - '05/31/2022 - 06/30/2022 | 12.00 usd - 12.00 usd [0] | 4.00 u - 4.00 u') + '05/31/2022 - 06/30/2022 | 12.00 usd - 12.00 usd [0] | 4.000 u - 4.000 u') # end ReconTestCase From 9f12861b79327bda69ba8fe8c1f635ad3d5ac208 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 30 Jan 2023 09:27:44 +0100 Subject: [PATCH 11/11] Version 6.0.3 --- README.rst | 5 +++++ tryton.cfg | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c77af18..7f909e4 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,11 @@ Requires Changes ======= +*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 diff --git a/tryton.cfg b/tryton.cfg index c7c153e..11e1c27 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.2 +version=6.0.3 depends: cashbook investment