diff --git a/__init__.py b/__init__.py index 9f96b97..163c612 100644 --- a/__init__.py +++ b/__init__.py @@ -6,8 +6,9 @@ from trytond.pool import Pool from .types import Type from .book import Book -from .line import Line from .reconciliation import Reconciliation +from .line import Line + def register(): Pool.register( diff --git a/line.py b/line.py index 9d155cb..9fd6c9c 100644 --- a/line.py +++ b/line.py @@ -10,6 +10,7 @@ from trytond.pyson import Eval, Or, If from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.modules.cashbook.line import STATES, DEPENDS +from .mixin import SecondUomMixin STATESQ = { 'required': Or( @@ -29,7 +30,7 @@ DEPENDSQ = DEPENDS+['feature', 'booktransf_feature', 'quantity_digits', 'bookingtype'] -class Line(metaclass=PoolMeta): +class Line(SecondUomMixin, metaclass=PoolMeta): __name__ = 'cashbook.line' quantity = fields.Numeric(string='Quantity', @@ -198,4 +199,4 @@ class Line(metaclass=PoolMeta): to_write.extend([lines, values]) super(Line, cls).write(*to_write) -# end LineContext +# end Line diff --git a/locale/de.po b/locale/de.po index 4c96b2f..caeda7a 100644 --- a/locale/de.po +++ b/locale/de.po @@ -154,6 +154,26 @@ msgctxt "help:cashbook.line,quantity_balance:" msgid "Number of shares in the cashbook up to the current row if the default sort applies." msgstr "Anzahl Anteile im Kassenbuch bis zur aktuellen Zeile, wenn die Standardsortierung zutrifft." +msgctxt "field:cashbook.line,quantity_2nd_uom:" +msgid "Quantity Second UOM" +msgstr "Anzahl in Fremdeinheit" + +msgctxt "field:cashbook.line,quantity2nd:" +msgid "2nd UOM" +msgstr "Fremdeinheit" + +msgctxt "field:cashbook.line,quantity2nd_digits:" +msgid "2nd UOM Digits" +msgstr "Dezimalstellen Fremdeinheit" + +msgctxt "field:cashbook.line,factor_2nd_uom:" +msgid "Conversion factor" +msgstr "Umrechnungsfaktor" + +msgctxt "help:cashbook.line,factor_2nd_uom:" +msgid "Conversion factor between the units of the participating cash books." +msgstr "Umrechnungsfaktor zwischen den Einheiten der teilnehmenden Kassenbücher." + ################## # cashbook.recon # diff --git a/mixin.py b/mixin.py new file mode 100644 index 0000000..e6b0c3a --- /dev/null +++ b/mixin.py @@ -0,0 +1,83 @@ +# -*- 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 fields +from trytond.pyson import Eval, Bool, Or +from trytond.pool import Pool +from trytond.transaction import Transaction +from trytond.modules.product.uom import uom_conversion_digits +from trytond.modules.cashbook.mixin import STATES, DEPENDS +from decimal import Decimal + +STATESQ = {} +STATESQ.update(STATES) +DEPENDSQ = [] +DEPENDSQ.extend(DEPENDS) + + +class SecondUomMixin(object): + """ two fields for second uom: quantity, rate + """ + quantity_2nd_uom = fields.Numeric(string='Quantity Second UOM', + digits=(16, Eval('quantity2nd_digits', 4)), + states={ + 'readonly': Or( + STATESQ['readonly'], + ~Bool(Eval('quantity2nd')) + ), + 'required': Bool(Eval('quantity2nd')), + 'invisible': ~Bool(Eval('quantity2nd')), + }, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']) + factor_2nd_uom = fields.Function(fields.Numeric(string='Conversion factor', + help='Conversion factor between the units of the participating cash books.', + digits=(16, uom_conversion_digits), + states={ + 'readonly': Or( + STATESQ['readonly'], + ~Bool(Eval('quantity2nd')) + ), + 'required': Bool(Eval('quantity2nd')), + 'invisible': ~Bool(Eval('quantity2nd')), + }, depends=DEPENDSQ+['quantity2nd_digits', 'quantity2nd']), + 'on_change_with_rate_2nd_uom', setter='set_rate_2nd_uom') + + quantity2nd = fields.Function(fields.Many2One(model_name='product.uom', + string="2nd UOM", readonly=True), 'on_change_with_quantity2nd') + quantity2nd_digits = fields.Function(fields.Integer(string='2nd UOM Digits', + readonly=True), 'on_change_with_quantity2nd_digits') + + @fields.depends('quantity', 'quantity_2nd_oum') + def on_change_with_factor_2nd_uom(self, name=None): + """ get factor from uom + """ + Rate = Pool().get('currency.currency.rate') + + if (self.quantity is not None) and \ + (self.quantity_2nd_oum is not None): + if self.quantity != Decimal('0.0'): + exp = Decimal(Decimal(1) / 10 ** Rate.rate.digits[1]) + return (self.quantity_2nd_oum / self.quantity).quantize(exp) + + @fields.depends('booktransf', '_parent_booktransf.quantity_uom', 'quantity_uom') + def on_change_with_quantity2nd(self, name=None): + """ uom of transfer-target + """ + if self.booktransf: + if self.quantity_uom: + if self.booktransf.quantity_uom: + if self.quantity_uom.id != \ + self.booktransf.quantity_uom.id: + return self.booktransf.quantity_uom.id + + @fields.depends('booktransf', '_parent_booktransf.quantity_digits') + def on_change_with_quantity2nd_digits(self, name=None): + """ uom of transfer-target + """ + if self.booktransf: + return self.booktransf.quantity_digits + else: + return 2 + +# end SecondUomMixin diff --git a/tests/test_book.py b/tests/test_book.py index b4441e3..990195b 100644 --- a/tests/test_book.py +++ b/tests/test_book.py @@ -538,7 +538,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): }]) @with_transaction() - def test_assetbook_check_bookingtype_mvout(self): + def test_assetbook_check_mvout(self): """ create cashbook + line, bookingtype 'mvout' transfer from cash to depot (buy asset, pay from cash) """ @@ -643,4 +643,113 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase): self.assertEqual(book2.lines[0].reference.rec_name, '05/01/2022|to|-1.00 usd|Transfer Out [Asset-Book | 1.00 usd | Open]') self.assertEqual(len(book2.lines[0].references), 0) + @with_transaction() + def test_assetbook_check_mvout_two_asset_accounts_invalid_category(self): + """ create cashbook + line, bookingtype 'mvout' + transfer from asset-book to asset-book, check deny of + invalid uom-catgories + """ + pool = Pool() + Book = pool.get('cashbook.book') + Line = pool.get('cashbook.line') + Category = pool.get('cashbook.category') + BType = pool.get('cashbook.type') + UOM = pool.get('product.uom') + Asset = pool.get('investment.asset') + ProdTempl = pool.get('product.template') + + type_cash = self.prep_type() + type_depot = self.prep_type('Depot', 'D') + BType.write(*[ + [type_depot], + { + 'feature': 'asset', + }]) + + category_in = self.prep_category(cattype='in') + category_out = self.prep_category(name='Out Category', cattype='out') + company = self.prep_company() + party = self.prep_party() + + asset1 = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 1')) + asset2 = self.prep_asset_item( + company=company, + product = self.prep_asset_product(name='Product 2')) + + uom_kg = UOM.search([('symbol', '=', 'kg')])[0] + uom_min = UOM.search([('symbol', '=', 'min')])[0] + ProdTempl.write(*[ + [asset1.product.template], + { + 'default_uom': uom_kg.id, + }, + [asset2.product.template], + { + 'default_uom': uom_min.id, + }, + ]) + + Asset.write(*[ + [asset1], + { + 'uom': uom_kg.id, + }, + [asset2], + { + 'uom': uom_min.id, + }, + ]) + self.assertEqual(asset1.symbol, 'usd/kg') + self.assertEqual(asset2.symbol, 'usd/min') + + book1, = Book.create([{ + 'name': 'Asset-Book - kg', + 'btype': type_depot.id, + 'asset': asset1.id, + 'quantity_uom': asset1.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + + book2, = Book.create([{ + 'name': 'Asset-Book - min', + 'btype': type_depot.id, + 'asset': asset2.id, + 'quantity_uom': asset2.uom.id, + 'company': company.id, + 'currency': company.currency.id, + 'number_sequ': self.prep_sequence().id, + 'start_date': date(2022, 5, 1), + }]) + self.assertEqual(book1.rec_name, 'Asset-Book - kg | 0.00 usd | Open') + self.assertEqual(book2.rec_name, 'Asset-Book - min | 0.00 usd | Open') + self.assertEqual(len(book1.lines), 0) + self.assertEqual(len(book2.lines), 0) + + Book.write(*[ + [book1], + { + 'lines': [('create', [{ + 'date': date(2022, 5, 1), + 'description': 'Transfer', + 'category': category_out.id, + 'bookingtype': 'mvout', + 'booktransf': book2.id, + 'amount': Decimal('1.0'), + 'quantity': Decimal('1.5'), + 'quantity_2nd_uom': Decimal('10.5'), + }])], + }]) + self.assertEqual(len(book1.lines), 1) + self.assertEqual(book1.lines[0].quantity_uom.symbol, 'kg') + self.assertEqual(book1.lines[0].quantity2nd.symbol, 'min') + self.assertEqual(book1.lines[0].quantity_digits, 4) + self.assertEqual(book1.lines[0].quantity2nd_digits, 4) + + Line.wfcheck(list(book1.lines)) + # end CbInvTestCase diff --git a/tests/test_reconciliation.py b/tests/test_reconciliation.py index aebd8cb..f2a37c7 100644 --- a/tests/test_reconciliation.py +++ b/tests/test_reconciliation.py @@ -17,7 +17,7 @@ class ReconTestCase(ModuleTestCase): @with_transaction() def test_recon_set_start_quantity_by_cashbook(self): - """ set stat-quantity from cashbook-setting + """ set start-quantity of reconciliation from cashbook-setting """ pool = Pool() Book = pool.get('cashbook.book')