From 82bdf3e05b4adac4fcd7ad10532f65c90a6ea068 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Thu, 2 Feb 2023 23:35:58 +0100 Subject: [PATCH] extend evaluation for cashbook-profit/loss amount/percent/diff - todo --- __init__.py | 6 +++ evaluation.py | 27 ++++++----- investment.py | 67 ++++++++++++++++++++++++++ line.py | 14 +++++- locale/de.po | 20 +++++++- locale/en.po | 20 +++++++- message.xml | 14 +++++- setup.py | 3 +- templates.py | 1 + tests/test_report.py | 111 ++++++++++++++++++++++++++++++++++++++++++- tryton.cfg | 3 +- 11 files changed, 265 insertions(+), 21 deletions(-) create mode 100644 investment.py diff --git a/__init__.py b/__init__.py index e4f23b7..4706fe2 100644 --- a/__init__.py +++ b/__init__.py @@ -9,6 +9,8 @@ from .line import EvaluationLine from .currency import Currency from .evaluation_context import EvaluationContext from .evaluation_wizard import OpenChartWizard +from .investment import InvestmentEvaluation, InvestmentLine + def register(): Pool.register( @@ -20,3 +22,7 @@ def register(): Pool.register( OpenChartWizard, module='cashbook_report', type_='wizard') + Pool.register( + InvestmentEvaluation, + InvestmentLine, + module='cashbook_report', type_='model', depends=['cashbook_investment']) diff --git a/evaluation.py b/evaluation.py index a25238a..513fc64 100644 --- a/evaluation.py +++ b/evaluation.py @@ -7,17 +7,11 @@ from trytond.model import ModelView, ModelSQL, fields, sequence_ordered from trytond.pyson import Eval from trytond.transaction import Transaction from trytond.pool import Pool +from trytond.i18n import gettext from .colors import sel_color as sel_bgcolor -from .templates import template_view_graph, template_view_line +from .templates import template_view_graph, template_view_line, cashbook_types -sel_etype = [ - ('cashbooks', 'Cashbooks'), - ('types', 'Types of Cashbooks'), - ('currencies', 'Currencys'), - ('categories', 'Categories'), - ] - sel_chart = [ ('vbar', 'Vertical Bars'), ('hbar', 'Horizontal Bars'), @@ -44,7 +38,7 @@ class Evaluation(sequence_ordered(), ModelSQL, ModelView): required=True, ondelete="RESTRICT") name = fields.Char(string='Name', required=True) dtype = fields.Selection(string='Data type', required=True, - sort=False, selection=sel_etype, + sort=True, selection='get_sel_etype', help='Type of data displayed') dtype_string = dtype.translated('dtype') chart = fields.Selection(string='Chart type', required=True, @@ -64,7 +58,7 @@ class Evaluation(sequence_ordered(), ModelSQL, ModelView): relation_name='cashbook_report.eval_line', origin='evaluation', target='cashbook', states={ - 'invisible': Eval('dtype', '') != 'cashbooks', + 'invisible': ~Eval('dtype', '').in_(cashbook_types), }, depends=['dtype']) types = fields.Many2Many(string='Types', relation_name='cashbook_report.eval_line', @@ -143,6 +137,17 @@ class Evaluation(sequence_ordered(), ModelSQL, ModelView): """ return 'pie' + @classmethod + def get_sel_etype(cls): + """ get list of evaluation-types + """ + return [ + ('cashbooks', gettext('cashbook_report.msg_dtype_cashbook')), + ('types', gettext('cashbook_report.msg_dtype_type')), + ('currencies', gettext('cashbook_report.msg_dtype_currency')), + ('categories', gettext('cashbook_report.msg_dtype_category')), + ] + @classmethod def get_create_view_data(cls, evaluation): """ generate dictionary to create view-xml @@ -285,7 +290,7 @@ class Evaluation(sequence_ordered(), ModelSQL, ModelView): continue for dt in ['cashbooks', 'types', 'currencies', 'categories']: - if (values['dtype'] != dt) and \ + if (not values['dtype'].startswith(dt)) and \ (len(getattr(evaluation, dt)) > 0): to_write.extend([ [evaluation], diff --git a/investment.py b/investment.py new file mode 100644 index 0000000..d67e24c --- /dev/null +++ b/investment.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# This file is part of the cashbook-module from m-ds for Tryton. +# The COPYRIGHT file at the top level of this repository contains the +# full copyright notices and license terms. + +from trytond.pool import PoolMeta, Pool +from trytond.i18n import gettext +from decimal import Decimal + + +class InvestmentEvaluation(metaclass=PoolMeta): + __name__ = 'cashbook_report.evaluation' + + @classmethod + def get_sel_etype(cls): + """ get list of evaluation-types + """ + result = super(InvestmentEvaluation, cls).get_sel_etype() + result.extend([ + ('cashbooks_gldiff', gettext('cashbook_report.msg_dtype_cashbook_gldiff')), + ('cashbooks_glperc', gettext('cashbook_report.msg_dtype_cashbook_glperc')), + ('cashbooks_glvalue', gettext('cashbook_report.msg_dtype_cashbooks_glvalue')) + ]) + return result + +# end InvestmentEvaluation + + +class InvestmentLine(metaclass=PoolMeta): + __name__ = 'cashbook_report.eval_line' + + def get_value_cashbooks_gldiff(self): + """ amount of profit/loss of cashbooks + """ + Currency = Pool().get('currency.currency') + + if self.cashbook: + if self.cashbook.feature == 'asset': + exp = Decimal(Decimal(1) / 10 ** self.currency_digits) + return Currency.compute( + self.cashbook.currency, + self.cashbook.diff_amount, + self.eval_currency, + ).quantize(exp) + + def get_value_cashbooks_glvalue(self): + """ current value of cashbooks + """ + Currency = Pool().get('currency.currency') + + if self.cashbook: + if self.cashbook.feature == 'asset': + exp = Decimal(Decimal(1) / 10 ** self.currency_digits) + return Currency.compute( + self.cashbook.currency, + self.cashbook.current_value, + self.eval_currency, + ).quantize(exp) + + def get_value_cashbooks_glperc(self): + """ percent of profit/loss of cashbooks + """ + if self.cashbook: + if self.cashbook.feature == 'asset': + return self.cashbook.diff_percent + +# end InvestmentLine diff --git a/line.py b/line.py index 78c9860..edb7b99 100644 --- a/line.py +++ b/line.py @@ -11,6 +11,7 @@ from trytond.transaction import Transaction from trytond.i18n import gettext from trytond.exceptions import UserError from trytond.pool import Pool +from .templates import cashbook_types class EvaluationLine(ModelSQL, ModelView): @@ -23,7 +24,7 @@ class EvaluationLine(ModelSQL, ModelView): cashbook = fields.Many2One(string='Cashbook', select=True, ondelete='CASCADE', model_name='cashbook.book', states={ - 'required': Eval('eval_dtype', '') == 'cashbooks', + 'required': Eval('eval_dtype', '').in_(cashbook_types), }, depends=['eval_dtype']) dtype = fields.Many2One(string='Type', select=True, ondelete='CASCADE', model_name='cashbook.type', @@ -130,6 +131,9 @@ class EvaluationLine(ModelSQL, ModelView): return getattr( getattr(self, { 'cashbooks': 'cashbook', + 'cashbooks_gldiff': 'cashbook', + 'cashbooks_glperc': 'cashbook', + 'cashbooks_glvalue': 'cashbook', 'categories': 'category', 'types': 'dtype', 'currencies': 'currency', @@ -142,7 +146,7 @@ class EvaluationLine(ModelSQL, ModelView): """ super(EvaluationLine, cls).validate(records) for record in records: - if (record.evaluation.dtype != 'cashbooks') and \ + if (record.evaluation.dtype not in cashbook_types) and \ (record.cashbook is not None): raise UserError(gettext( 'cashbook_report.msg_invalid_dtype', @@ -160,6 +164,12 @@ class EvaluationLine(ModelSQL, ModelView): 'cashbook_report.msg_invalid_dtype', typename = gettext('cashbook_report.msg_dtype_currency'), )) + if (record.evaluation.dtype != 'categories') and \ + (record.category is not None): + raise UserError(gettext( + 'cashbook_report.msg_invalid_dtype', + typename = gettext('cashbook_report.msg_dtype_category'), + )) def get_balance_by_query(self, query): """ run 'query' on Lines, convert used diff --git a/locale/de.po b/locale/de.po index b770a6f..c3c2744 100644 --- a/locale/de.po +++ b/locale/de.po @@ -11,8 +11,20 @@ msgid "Type of evaluation must be '%(typename)s'." msgstr "Typ der Auswertung mus '%(typename)s' sein." msgctxt "model:ir.message,text:msg_dtype_cashbook" -msgid "Cashbooks" -msgstr "Kassenbücher" +msgid "Cashbooks [Amount]" +msgstr "Kassenbücher [Betrag]" + +msgctxt "model:ir.message,text:msg_dtype_cashbook_gldiff" +msgid "Cashbooks [Amount of Profit/Loss]" +msgstr "Kassenbücher [Betrag Gewinn/Verlust]" + +msgctxt "model:ir.message,text:msg_dtype_cashbook_glperc" +msgid "Cashbooks [Percent of Profit/Loss]" +msgstr "Kassenbücher [Prozent Gewinn/Verlust]" + +msgctxt "model:ir.message,text:msg_dtype_cashbooks_glvalue" +msgid "Cashbooks [Current Value]" +msgstr "Kassenbücher [aktueller Wert]" msgctxt "model:ir.message,text:msg_dtype_type" msgid "Types of Cashbooks" @@ -22,6 +34,10 @@ msgctxt "model:ir.message,text:msg_dtype_currency" msgid "Currencies" msgstr "Währungen" +msgctxt "model:ir.message,text:msg_dtype_category" +msgid "Categories" +msgstr "Kategorien" + msgctxt "model:ir.message,text:msg_name_graph" msgid "Graph: %(gname)s" msgstr "Diagramm: %(gname)s" diff --git a/locale/en.po b/locale/en.po index 67ddf0f..0966cbb 100644 --- a/locale/en.po +++ b/locale/en.po @@ -7,8 +7,20 @@ msgid "Type of evaluation must be '%(typename)s'." msgstr "Type of evaluation must be '%(typename)s'." msgctxt "model:ir.message,text:msg_dtype_cashbook" -msgid "Cashbooks" -msgstr "Cashbooks" +msgid "Cashbooks [Amount]" +msgstr "Cashbooks [Amount]" + +msgctxt "model:ir.message,text:msg_dtype_cashbook_gldiff" +msgid "Cashbooks [Amount of Profit/Loss]" +msgstr "Cashbooks [Amount of Profit/Loss]" + +msgctxt "model:ir.message,text:msg_dtype_cashbook_glperc" +msgid "Cashbooks [Percent of Profit/Loss]" +msgstr "Cashbooks [Percent of Profit/Loss]" + +msgctxt "model:ir.message,text:msg_dtype_cashbooks_glvalue" +msgid "Cashbooks [Current Value]" +msgstr "Cashbooks [Current Value]" msgctxt "model:ir.message,text:msg_dtype_type" msgid "Types of Cashbooks" @@ -18,6 +30,10 @@ msgctxt "model:ir.message,text:msg_dtype_currency" msgid "Currencies" msgstr "Currencies" +msgctxt "model:ir.message,text:msg_dtype_category" +msgid "Categories" +msgstr "Categories" + msgctxt "model:ir.message,text:msg_name_graph" msgid "Graph: %(gname)s" msgstr "Graph: %(gname)s" diff --git a/message.xml b/message.xml index 2b66901..ec34ead 100644 --- a/message.xml +++ b/message.xml @@ -9,7 +9,16 @@ full copyright notices and license terms. --> Type of evaluation must be '%(typename)s'. - Cashbooks + Cashbooks [Amount] + + + Cashbooks [Amount of Profit/Loss] + + + Cashbooks [Percent of Profit/Loss] + + + Cashbooks [Current Value] Types of Cashbooks @@ -17,6 +26,9 @@ full copyright notices and license terms. --> Currencies + + Categories + Graph: %(gname)s diff --git a/setup.py b/setup.py index 5b7501b..b1a0cf1 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ with open(path.join(here, 'README.rst'), encoding='utf-8') as f: # tryton.cfg einlesen config = ConfigParser() -config.readfp(open('tryton.cfg')) +config.read_file(open('tryton.cfg')) info = dict(config.items('tryton')) for key in ('depends', 'extras_depend', 'xml'): if key in info: @@ -68,6 +68,7 @@ setup(name='%s_%s' % (PREFIX, MODULE), description='Tryton module to add reports to cashbook.', long_description=long_description, url='https://www.m-ds.de/', + download_url='https://scmdev.m-ds.de/Tryton/Extra/cashbook_report', author='martin-data services', author_email='service@m-ds.de', license='GPL-3', diff --git a/templates.py b/templates.py index d3432f9..95e3857 100644 --- a/templates.py +++ b/templates.py @@ -3,6 +3,7 @@ # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. +cashbook_types = ['cashbooks', 'cashbooks_gldiff', 'cashbooks_glperc', 'cashbooks_glvalue'] template_view_line = '' diff --git a/tests/test_report.py b/tests/test_report.py index 7d8f2d4..c75cbe2 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -510,6 +510,31 @@ class ReportTestCase(CashbookTestCase): self.assertEqual(len(evaluation.currencies), 0) self.assertEqual(len(evaluation.categories), 0) + # investment - profit/loss amount + if 'cashbooks_gldiff' in [x[0] for x in Evaluation.get_sel_etype()]: + # no change if switch between cashbook-types + Evaluation.write(*[ + [evaluation], + { + 'dtype': 'cashbooks_gldiff', + }]) + self.assertEqual(len(evaluation.cashbooks), 3) + self.assertEqual(len(evaluation.types), 0) + self.assertEqual(len(evaluation.currencies), 0) + self.assertEqual(len(evaluation.categories), 0) + + Evaluation.write(*[ + [evaluation], + { + 'dtype': 'cashbooks_glperc', + }]) + self.assertEqual(len(evaluation.cashbooks), 3) + self.assertEqual(len(evaluation.types), 0) + self.assertEqual(len(evaluation.currencies), 0) + self.assertEqual(len(evaluation.categories), 0) + else : + print('\n--== Module "cashbook_investment" not installed ==--') + Evaluation.write(*[ [evaluation], { @@ -684,6 +709,90 @@ class ReportTestCase(CashbookTestCase): if DashboardAction is not None: self.assertEqual(DashboardAction.search_count([]), 1) + @with_transaction() + def test_report_chart_hbar_book_investment(self): + """ create 3x cashbooks, add bookings, rates + create report with cashbooks, check + """ + pool = Pool() + Evaluation = pool.get('cashbook_report.evaluation') + Book = pool.get('cashbook.book') + + company = self.prep_company() + with Transaction().set_context({ + 'company': company.id, + }): + if 'cashbooks_gldiff' not in [x[0] for x in Evaluation.get_sel_etype()]: + print('\n--== Module "cashbook_investment" not installed ==--') + return + + AccType = pool.get('cashbook.type') + Asset = pool.get('investment.asset') + Product = pool.get('product.template') + Uom = pool.get('product.uom') + + at, = AccType.create([{ + 'name': 'depot', + 'short': 'D', + 'feature': 'asset', + 'company': company.id, + }]) + + prod_templ, = Product.create([{ + 'name': 'Aurum', + 'type': 'assets', + 'list_price': Decimal('1.0'), + 'default_uom': Uom.search([('symbol', '=', 'u')])[0].id, + 'products': [('create', [{ + 'description': 'Au', + }])], + }]) + + asset, = Asset.create([{ + 'company': company.id, + 'product': prod_templ.products[0].id, + 'currency': company.currency.id, + 'currency_digits': 4, + 'uom': prod_templ.default_uom.id, + 'rates': [('create', [{ + 'date': date(2022, 5, 1), + 'rate': Decimal('1750.0'), + }, ])], + }]) + + AccType.write(*[ + [books[0].btype], + { + 'feature': 'asset', + }]) + + books = self.prep_report_3books() + Book.write(*[ + books, + { + 'asset': asset.id, + 'quantity_uom': asset.uom.id, + 'quantity_digits': 3, + }]) + self.assertEqual(books[0].rec_name, 'ss') + + evaluation, = Evaluation.create([{ + 'name': 'Evaluation 1', + 'dtype': 'cashbooks_gldiff', + 'chart': 'hbar', + 'cashbooks': [('add', [x.id for x in books])], + }]) + self.assertEqual(evaluation.dtype, 'cashbooks_gldiff') + self.assertEqual(evaluation.chart, 'hbar') + self.assertEqual(evaluation.legend, True) + self.assertEqual(evaluation.maincolor, 'default') + self.assertEqual(evaluation.bgcolor, '#ffffc0') + self.assertEqual(evaluation.currency.code, 'EUR') + + self.assertEqual(evaluation.line_values[0].balance, Decimal('23.81')) + self.assertEqual(evaluation.line_values[1].balance, Decimal('11.90')) + self.assertEqual(evaluation.line_values[2].balance, Decimal('23.00')) + @with_transaction() def test_report_chart_pie_book_red(self): """ create 3x cashbooks, add bookings, @@ -719,7 +828,7 @@ class ReportTestCase(CashbookTestCase): - + """) diff --git a/tryton.cfg b/tryton.cfg index 22b19dd..a53b760 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -4,8 +4,9 @@ depends: res cashbook cashbook_bookcategory -extras_depend: +#extras_depend: dashboard + cashbook_investment xml: icon.xml message.xml