From 269e744b8f6dbcbae9759de5f383d0bd63ad36e3 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sat, 21 Jan 2023 19:18:36 +0100 Subject: [PATCH 01/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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/60] 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 From e8d69b64ae6a6c6e64a7356d6e258a19bf1acc75 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Wed, 1 Feb 2023 14:29:48 +0100 Subject: [PATCH 13/60] line/splitline: fixed selection of quantity-uom/digits --- line.py | 27 ++++++++++---- splitline.py | 23 ++++++++---- tests/test_book.py | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 14 deletions(-) diff --git a/line.py b/line.py index adfc1bd..517bea2 100644 --- a/line.py +++ b/line.py @@ -314,20 +314,33 @@ class Line(SecondUomMixin, metaclass=PoolMeta): self.amount / self.quantity ).quantize(Decimal(Decimal(1) / 10**digit)) - @fields.depends('cashbook', '_parent_cashbook.quantity_uom') + @fields.depends('feature', 'cashbook', '_parent_cashbook.quantity_uom', \ + 'booktransf', '_parent_booktransf.quantity_uom', '_parent_booktransf.feature') def on_change_with_quantity_uom(self, name=None): """ get quantity-unit of asset """ - if self.cashbook: - if self.cashbook.quantity_uom: - return self.cashbook.quantity_uom.id + if self.feature == 'asset': + if self.cashbook: + if self.cashbook.quantity_uom: + return self.cashbook.quantity_uom.id + else : + if self.booktransf: + if self.booktransf.feature == 'asset': + if self.booktransf.quantity_uom: + return self.booktransf.quantity_uom.id - @fields.depends('cashbook', '_parent_cashbook.quantity_digits') + @fields.depends('feature', 'cashbook', '_parent_cashbook.quantity_digits', \ + 'booktransf', '_parent_booktransf.quantity_digits', '_parent_booktransf.feature') def on_change_with_quantity_digits(self, name=None): """ get digits from cashbook """ - if self.cashbook: - return self.cashbook.quantity_digits + if self.feature == 'asset': + if self.cashbook: + return self.cashbook.quantity_digits + else : + if self.booktransf: + if self.booktransf.feature == 'asset': + return self.booktransf.quantity_digits return 4 @classmethod diff --git a/splitline.py b/splitline.py index efeca8b..539124d 100644 --- a/splitline.py +++ b/splitline.py @@ -47,23 +47,32 @@ class SplitLine(SecondUomMixin, metaclass=PoolMeta): } return recname - @fields.depends('line', '_parent_line.cashbook', 'booktransf', '_parent_booktransf.quantity_uom') + @fields.depends('line', '_parent_line.cashbook', 'booktransf', \ + '_parent_booktransf.feature', '_parent_booktransf.quantity_uom') def on_change_with_quantity_uom(self, name=None): """ get quantity-unit of asset """ if self.line: - if self.line.cashbook.quantity_uom: - return self.cashbook.quantity_uom.id + if self.line.cashbook.feature == 'asset': + if self.line.cashbook.quantity_uom: + return self.cashbook.quantity_uom.id if self.booktransf: - if self.booktransf.quantity_uom: - return self.booktransf.quantity_uom.id + if self.booktransf.feature == 'asset': + if self.booktransf.quantity_uom: + return self.booktransf.quantity_uom.id - @fields.depends('line', '_parent_line.cashbook') + @fields.depends('line', '_parent_line.cashbook', 'booktransf', \ + '_parent_booktransf.feature', '_parent_booktransf.quantity_digits') def on_change_with_quantity_digits(self, name=None): """ get digits from cashbook """ if self.line: - return self.line.cashbook.quantity_digits + if self.line.cashbook.feature == 'asset': + return self.line.cashbook.quantity_digits + if self.booktransf: + if self.booktransf.feature == 'asset': + return self.booktransf.quantity_digits + return 4 @classmethod diff --git a/tests/test_book.py b/tests/test_book.py index 08e8f80..01df667 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -585,6 +585,96 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): book.on_change_asset() self.assertEqual(book.quantity_uom.rec_name, 'Unit') + @with_transaction() + def test_assetbook_quantity_digits(self): + """ check selection of quantity-digits + """ + pool = Pool() + Book = pool.get('cashbook.book') + BType = pool.get('cashbook.type') + + company = self.prep_company() + category = self.prep_category(cattype='in') + type_depot = self.prep_type('Depot', 'D') + type_cash = self.prep_type('Cash', 'C') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + 'name': 'Asset', + 'short': 'A', + }]) + + asset = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 1')) + self.assertEqual(asset.symbol, 'usd/u') + + books = Book.create([{ + 'name': 'Book 1', + 'btype': type_depot.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, + 'start_date': date(2022, 5, 1), + }, { + 'name': 'Book 2', + 'btype': type_cash.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + self.assertEqual(books[0].rec_name, 'Book 1 | 0.00 usd | Open | 0.000 u') + self.assertEqual(books[1].rec_name, 'Book 2 | 0.00 usd | Open') + + Book.write(*[ + [books[0]], + { + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'category': category.id, + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.0'), + }])], + }]) + self.assertEqual(books[0].lines[0].rec_name, + '05/01/2022|Rev|1.00 usd|- [Cat1]|1.000 u') + self.assertEqual(books[0].lines[0].quantity_digits, 3) + + Book.write(*[ + [books[1]], + { + 'lines': [('create', [{ + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'category': category.id, + 'amount': Decimal('1.0'), + }])], + }]) + self.assertEqual(books[1].lines[0].rec_name, + '05/01/2022|Rev|1.00 usd|- [Cat1]') + self.assertEqual(books[1].lines[0].quantity_digits, 4) + + Book.write(*[ + [books[1]], + { + 'lines': [('create', [{ + 'bookingtype': 'mvin', + 'date': date(2022, 5, 1), + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.0'), + 'booktransf': books[0].id, + }])], + }]) + self.assertEqual(books[1].lines[1].rec_name, + '05/01/2022|from|1.00 usd|- [Book 1 | 1.00 usd | Open | 1.000 u]') + self.assertEqual(books[1].lines[1].quantity_digits, 3) + @with_transaction() def test_assetbook_book_with_asset(self): """ create cashbook, set 'btype' to asset From e65c961e61f51fa264664e9824581db1db7514b0 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Wed, 1 Feb 2023 14:51:47 +0100 Subject: [PATCH 14/60] Version 6.0.4 --- README.rst | 4 ++++ tryton.cfg | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7f909e4..9f4cb79 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,10 @@ Requires Changes ======= +*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 diff --git a/tryton.cfg b/tryton.cfg index 11e1c27..dcc6aeb 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.3 +version=6.0.4 depends: cashbook investment From 85d385901f078a6eea67bcdfbc49a90be9ef9a33 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 5 Feb 2023 11:35:55 +0100 Subject: [PATCH 16/60] line: fix write() - get 'bookingtype' from line if not in values --- line.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/line.py b/line.py index 517bea2..8c7b862 100644 --- a/line.py +++ b/line.py @@ -150,10 +150,10 @@ class Line(SecondUomMixin, metaclass=PoolMeta): cashbook = Cashbook.browse([id_cashbook])[0] if isinstance(values, dict): - type_ = values.get('bookingtype', None) + type_ = values.get('bookingtype', getattr(line, 'bookingtype', None)) quantity = values.get('quantity', None) else : - type_ = getattr(values, 'bookingtype', None) + type_ = getattr(values, 'bookingtype', getattr(line, 'bookingtype', None)) quantity = getattr(values, 'quantity', None) if (type_ is not None) and (cashbook.feature == 'asset'): @@ -170,6 +170,7 @@ class Line(SecondUomMixin, metaclass=PoolMeta): }) else : raise ValueError('invalid "bookingtype"') + return result @classmethod From a7a9f9ef3db1512db57a26ead87f9e0ee172e135 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 5 Feb 2023 18:10:00 +0100 Subject: [PATCH 17/60] Version 6.0.5 --- README.rst | 4 ++++ tryton.cfg | 2 +- versiondep.txt | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 9f4cb79..acf6b53 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,10 @@ Requires Changes ======= +*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 diff --git a/tryton.cfg b/tryton.cfg index dcc6aeb..dc3ee72 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.4 +version=6.0.5 depends: cashbook investment diff --git a/versiondep.txt b/versiondep.txt index e277012..4afe3a6 100644 --- a/versiondep.txt +++ b/versiondep.txt @@ -1,2 +1,2 @@ -cashbook;6.0.23;6.0.999;mds +cashbook;6.0.24;6.0.999;mds investment;6.0.23;6.0.999;mds From c70d2fef7a784973c6c86805af9b864863230691 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Sun, 12 Feb 2023 00:09:56 +0100 Subject: [PATCH 19/60] line: add fields 'Fee' + 'Dividend' - todos --- __init__.py | 2 + assetsetting.py | 24 +++++ assetsetting.xml | 53 ++++++++++ line.py | 137 +++++++++++++++++++++++++ locale/de.po | 60 +++++++++++ locale/en.po | 36 +++++++ menu.xml | 18 ++++ tests/__init__.py | 2 + tests/test_yield.py | 215 ++++++++++++++++++++++++++++++++++++++++ tryton.cfg | 2 + view/assetconf_form.xml | 14 +++ view/line_form.xml | 6 ++ 12 files changed, 569 insertions(+) create mode 100644 assetsetting.py create mode 100644 assetsetting.xml create mode 100644 menu.xml create mode 100644 tests/test_yield.py create mode 100644 view/assetconf_form.xml diff --git a/__init__.py b/__init__.py index a5cbea0..a11da2c 100644 --- a/__init__.py +++ b/__init__.py @@ -9,6 +9,7 @@ from .book import Book from .reconciliation import Reconciliation from .line import Line from .splitline import SplitLine +from .assetsetting import AssetSetting def register(): @@ -18,4 +19,5 @@ def register(): Line, SplitLine, Reconciliation, + AssetSetting, module='cashbook_investment', type_='model') diff --git a/assetsetting.py b/assetsetting.py new file mode 100644 index 0000000..f9846a8 --- /dev/null +++ b/assetsetting.py @@ -0,0 +1,24 @@ +# -*- 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 + + +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.') + +# 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/line.py b/line.py index 8c7b862..3682edb 100644 --- a/line.py +++ b/line.py @@ -4,12 +4,14 @@ # full copyright notices and license terms. from decimal import Decimal +from sql.conditionals import Coalesce from trytond.model import fields from trytond.pool import PoolMeta, Pool from trytond.pyson import Eval, Or, If, And from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.report import Report +from trytond.transaction import Transaction from trytond.modules.cashbook.line import STATES, DEPENDS from .mixin import SecondUomMixin @@ -108,6 +110,141 @@ class Line(SecondUomMixin, metaclass=PoolMeta): }, depends=['currency_digits', 'feature']), 'on_change_with_diff_percent') + trade_fee = fields.Function(fields.Numeric(string='Fee', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states = { + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'get_yield_data') + asset_dividend = fields.Function(fields.Numeric(string='Dividend', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states = { + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'get_yield_data') + asset_gainloss = fields.Function(fields.Numeric(string='Profit/Loss', + readonly=True, digits=(16, Eval('currency_digits', 2)), + states = { + 'invisible': Eval('feature', '') != 'asset', + }, depends=['currency_digits', 'feature']), + 'get_yield_data') + + @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_line1 = cls.__table__() + tab_line2 = cls.__table__() + tab_line_fee = cls.__table__() + tab_line_divi = cls.__table__() + tab_spline_fee = SplitLine.__table__() + tab_spline_divi = SplitLine.__table__() + + cfg1 = AssetSetting.get_singleton() + + # local booked fee/dividend + tab_inout = cls.search([ + ('cashbook.btype.feature', '=', 'asset'), + ('bookingtype', 'in', ['in', 'out']), + ('category', '!=', None), + ], query=True) + query_inout = tab_inout.join(tab_line_fee, + condition=(tab_line_fee.id==tab_inout.id) & \ + (tab_line_fee.category == getattr(cfg1.fee_category, 'id', None)), + type_ = 'LEFT OUTER', + ).join(tab_line_divi, + condition=(tab_line_divi.id==tab_inout.id) & \ + (tab_line_divi.category == getattr(cfg1.dividend_category, 'id', None)), + type_ = 'LEFT OUTER', + ).select( + tab_inout.id, + (tab_line_fee.credit - tab_line_fee.debit).as_('fee'), + (tab_line_divi.credit - tab_line_divi.debit).as_('dividend'), + ) + + # fee/dividend - in counterpart booking + tab_mvinout = cls.search([ + ('cashbook.btype.feature', '=', 'asset'), + ('bookingtype', 'in', ['mvin', 'mvout']), + ], query=True) + query_mvinout = tab_mvinout.join(tab_line1, + condition=tab_mvinout.id==tab_line1.id, + ).join(tab_line2, + # current line is linked to split-booking-line of counterpart + condition=((tab_line1.reference == tab_line2.id) | \ + (tab_line1.id == tab_line2.reference)) & \ + (tab_line2.bookingtype.in_(['spin', 'spout'])), + ).join(tab_spline_fee, + # fee-line is linked to split-booking-line + condition=(tab_spline_fee.line == tab_line2.id) & \ + (tab_spline_fee.splittype == 'cat') & \ + (tab_spline_fee.category != None) & \ + (tab_spline_fee.category == getattr(cfg1.fee_category, 'id', None)), + type_ = 'LEFT OUTER', + ).join(tab_spline_divi, + # dividend-line is linked to split-booking-line + condition=(tab_spline_divi.line == tab_line2.id) & \ + (tab_spline_divi.splittype == 'cat') & \ + (tab_spline_divi.category != None) & \ + (tab_spline_divi.category == getattr(cfg1.dividend_category, 'id', None)), + type_ = 'LEFT OUTER', + ).select( + tab_line1.id, + tab_spline_fee.amount.as_('fee'), + tab_spline_divi.amount.as_('dividend'), + ) + + # together + query = tab_line.join(query_inout, + condition=query_inout.id==tab_line.id, + type_ = 'LEFT OUTER', + ).join(query_mvinout, + condition=query_mvinout.id==tab_line.id, + type_ = 'LEFT OUTER', + ).select( + tab_line.id, + Coalesce(query_inout.fee, query_mvinout.fee).as_('fee'), + Coalesce(query_inout.dividend, query_mvinout.dividend).as_('dividend'), + ) + return (tab_line, query) + + @classmethod + def get_yield_data(cls, lines, names): + """ collect data for fee, dividend, gain/loss per line + """ + Line2 = Pool().get('cashbook.line') + (tab_line, query) = cls.get_yield_data_sql() + 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} + 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), + 'asset_gainloss': Decimal('0.0'), #quantize_val(record[3], line), + } + + for name in names: + result[name][record[0]] = values[name] + + return result + def get_rec_name(self, name): """ add quantities - if its a asset-cashbook """ diff --git a/locale/de.po b/locale/de.po index cc86f22..d23cc63 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 # ################# @@ -246,6 +262,18 @@ 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 "field:cashbook.line,asset_dividend:" +msgid "Dividend" +msgstr "Dividende" + +msgctxt "field:cashbook.line,asset_gainloss:" +msgid "Profit/Loss" +msgstr "Gewinn/Verlust" + ################## # cashbook.recon # @@ -265,3 +293,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 ce29b4e..585c663 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" @@ -242,3 +250,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/tests/__init__.py b/tests/__init__.py index 21885a8..97f40bc 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -6,6 +6,7 @@ import unittest from trytond.modules.cashbook_investment.tests.test_book import CbInvTestCase from trytond.modules.cashbook_investment.tests.test_reconciliation import ReconTestCase +from trytond.modules.cashbook_investment.tests.test_yield import YieldTestCase __all__ = ['suite'] @@ -14,6 +15,7 @@ __all__ = ['suite'] class CashbookInvestmentTestCase(\ CbInvTestCase,\ ReconTestCase,\ + YieldTestCase,\ ): 'Test cashbook-investment module' module = 'cashbook_investment' diff --git a/tests/test_yield.py b/tests/test_yield.py new file mode 100644 index 0000000..2a8568a --- /dev/null +++ b/tests/test_yield.py @@ -0,0 +1,215 @@ +# -*- 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 ModuleTestCase, with_transaction + + +class YieldTestCase(ModuleTestCase): + 'Test yield calculation module' + module = 'cashbook_investment' + + 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, + }]) + + 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, + 'GainLoss | 0.00 usd | Open') + return as_cfg + + @with_transaction() + def test_yield_config(self): + """ check config + """ + pool = Pool() + AssetConf = pool.get('cashbook.assetconf') + company = self.prep_company() + + 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_dividend_gainloss(self): + """ add cashbook, categories, bookings for + fees, dididend... + """ + 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() + as_cfg = self.prep_yield_config('Fee', 'Dividend', 'GainLoss', 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_office, = Category.create([{ + 'name': 'Office', + 'company': company.id, + }]) + + 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': 'out', + 'date': date(2022, 5, 1), + 'amount': Decimal('2.0'), + 'quantity': Decimal('0.0'), + 'category': as_cfg.fee_category.id, + 'description': 'Fee', + }, { + 'bookingtype': 'in', + 'date': date(2022, 5, 1), + 'amount': Decimal('10.0'), + 'quantity': Decimal('1.0'), + 'category': as_cfg.dividend_category.id, + 'description': 'reinvested dividend', + }])], + }]) + + # dividend, incoming + link to depot-account + 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), + 'lines': [('create', [{ + 'bookingtype': 'spin', + 'date': date(2022, 5, 1), + 'description': 'Dividend', + 'splitlines': [('create', [{ + 'description': 'Dividend', + 'splittype': 'cat', + 'category': as_cfg.dividend_category.id, + 'amount': Decimal('5.0'), + }, { + 'description': 'Dividend', + 'splittype': 'tr', + 'booktransf': book_asset.id, + 'amount': Decimal('0.0'), + 'quantity': Decimal('0.0'), + }])], + }])], + }]) + self.assertEqual(len(book_cash.lines), 1) + Line.wfcheck([book_cash.lines[0]]) + + self.assertEqual(book_cash.lines[0].rec_name, + '05/01/2022|Rev/Sp|5.00 usd|Dividend [-]') + self.assertEqual(book_cash.lines[0].reference, None) + self.assertEqual(len(book_cash.lines[0].references), 1) + self.assertEqual(book_cash.lines[0].references[0].rec_name, + '05/01/2022|to|0.00 usd|Dividend [Cash | 5.00 usd | Open]|0.0000 u') + + self.assertEqual(book_asset.name, 'Depot') + self.assertEqual(book_asset.rec_name, 'Depot | 8.00 usd | Open | 1.0000 u') + self.assertEqual(book_asset.btype.rec_name, 'D - Depot') + self.assertEqual(book_asset.state, 'open') + self.assertEqual(book_asset.feature, 'asset') + self.assertEqual(len(book_asset.lines), 3) + + # reference to dividend (1) + self.assertEqual(book_asset.lines[0].rec_name, + '05/01/2022|to|0.00 usd|Dividend [Cash | 5.00 usd | Open]|0.0000 u') + self.assertEqual(book_asset.lines[0].trade_fee, Decimal('0.0')) + self.assertEqual(book_asset.lines[0].asset_dividend, Decimal('5.0')) + self.assertEqual(book_asset.lines[0].asset_gainloss, Decimal('0.0')) + + # fee payed from asset-account + self.assertEqual(book_asset.lines[1].rec_name, + '05/01/2022|Exp|-2.00 usd|Fee [Fee]|0.0000 u') + self.assertEqual(book_asset.lines[1].trade_fee, Decimal('-2.0')) + self.assertEqual(book_asset.lines[1].asset_dividend, Decimal('0.0')) + self.assertEqual(book_asset.lines[1].asset_gainloss, Decimal('0.0')) + + # dividend (2) added to asset-account + self.assertEqual(book_asset.lines[2].rec_name, + '05/01/2022|Rev|10.00 usd|reinvested dividend [Dividend]|1.0000 u') + self.assertEqual(book_asset.lines[2].trade_fee, Decimal('0.0')) + self.assertEqual(book_asset.lines[2].asset_dividend, Decimal('10.0')) + self.assertEqual(book_asset.lines[2].asset_gainloss, Decimal('0.0')) + +# end YieldTestCase diff --git a/tryton.cfg b/tryton.cfg index dc3ee72..40fa87c 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -9,3 +9,5 @@ xml: line.xml reconciliation.xml splitline.xml + assetsetting.xml + menu.xml 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 @@ + + +
+ +