diff --git a/book.py b/book.py
index 7db0871..e480030 100644
--- a/book.py
+++ b/book.py
@@ -11,8 +11,10 @@ from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.report import Report
from decimal import Decimal
+from sql import Literal
from sql.aggregate import Sum
-from sql.conditionals import Case
+from sql.conditionals import Case, Coalesce
+from sql.functions import CurrentDate
from .model import order_name_hierarchical
@@ -103,15 +105,29 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
balance = fields.Function(fields.Numeric(string='Balance', readonly=True,
digits=(16, Eval('currency_digits', 2)),
depends=['currency_digits']), 'on_change_with_balance')
- currency = fields.Many2One(string='Currency',
- model_name='currency.currency',
+
+ balance_ref = fields.Function(fields.Numeric(string='Balance (Ref.)',
+ help='Balance in company currency',
+ readonly=True, digits=(16, Eval('company_currency_digits', 2)),
+ states={
+ 'invisible': ~Bool(Eval('company_currency')),
+ }, depends=['company_currency_digits', 'company_currency']),
+ 'on_change_with_balance_ref')
+ company_currency = fields.Function(fields.Many2One(readonly=True,
+ string='Company Currency', states={'invisible': True},
+ model_name='currency.currency'),
+ 'on_change_with_company_currency')
+ company_currency_digits = fields.Function(fields.Integer(
+ string='Currency Digits (Ref.)', readonly=True),
+ 'on_change_with_currency_digits')
+
+ currency = fields.Many2One(string='Currency', select=True,
+ model_name='currency.currency', readonly=True,
states={
'readonly': Or(
STATES2['readonly'],
Bool(Eval('lines', [])),
),
- 'invisible': STATES2['invisible'],
- 'required': ~STATES2['invisible'],
}, depends=DEPENDS2+['lines'])
currency_digits = fields.Function(fields.Integer(string='Currency Digits',
readonly=True), 'on_change_with_currency_digits')
@@ -255,36 +271,95 @@ class Book(tree(separator='/'), Workflow, ModelSQL, ModelView):
else:
return 2
+ @fields.depends('company', 'currency', 'btype')
+ def on_change_with_company_currency(self, name=None):
+ """ get company-currency if its different from current
+ cashbook-currency, disable if book is a view
+ """
+ if self.company:
+ if self.currency:
+ if self.btype:
+ if self.company.currency.id != self.currency.id:
+ return self.company.currency.id
+
@fields.depends('id')
def on_change_with_balance(self, name=None):
""" compute balance
"""
pool = Pool()
Book2 = pool.get('cashbook.book')
+ Book3 = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
+ Currency = pool.get('currency.currency')
+
tab_line = Line.__table__()
+ tab_book = Book3.__table__()
cursor = Transaction().connection.cursor()
+ # select cashbook-lines from current cashbook and below
line_query = Line.search([
('cashbook.id', 'in', Book2.search([
('parent', 'child_of', [self.id]),
], query=True)),
], query=True)
- query = line_query.join(tab_line,
+ # sum lines by currency
+ bal_by_currency = line_query.join(tab_line,
condition=tab_line.id==line_query.id,
+ ).join(tab_book,
+ condition=tab_book.id==tab_line.cashbook,
).select(
Sum(tab_line.credit - tab_line.debit).as_('balance'),
+ tab_book.currency,
+ group_by=[tab_book.currency],
+ )
+
+ if self.id:
+ total = Decimal('0.0')
+
+ cursor.execute(*bal_by_currency)
+ balance_lines = cursor.fetchall()
+
+ for line in balance_lines:
+ (balance, id_currency) = line
+
+ total += Currency.compute(
+ Currency(id_currency), # from
+ balance,
+ self.currency, # to
+ )
+ return total
+
+ @fields.depends('company', 'currency', 'id', 'btype')
+ def on_change_with_balance_ref(self, name=None):
+ """ balance converted to company-currency
+ """
+ pool = Pool()
+ Line = pool.get('cashbook.line')
+ tab_line = Line.__table__()
+ cursor = Transaction().connection.cursor()
+
+ if self.btype is None:
+ return None
+
+ query = tab_line.select(
+ Sum(tab_line.credit - tab_line.debit),
+ where = tab_line.cashbook == self.id,
)
if self.id:
- balance = Decimal('0.0')
cursor.execute(*query)
result = cursor.fetchone()
+ balance = Decimal('0.0')
if result:
if result[0] is not None:
balance += result[0]
- return balance
+ if self.currency:
+ return self.currency.compute(
+ self.currency, # from
+ balance,
+ self.company.currency # to
+ )
@classmethod
@ModelView.button
diff --git a/locale/de.po b/locale/de.po
index ab6761f..3fac21d 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -554,6 +554,22 @@ msgctxt "field:cashbook.book,right:"
msgid "Right"
msgstr "Rechts"
+msgctxt "field:cashbook.book,balance_ref:"
+msgid "Balance (Ref.)"
+msgstr "Saldo (Ref.)"
+
+msgctxt "help:cashbook.book,balance_ref:"
+msgid "Balance in company currency"
+msgstr "Saldo in der Unternehmenswährung"
+
+msgctxt "field:cashbook.book,company_currency:"
+msgid "Company Currency"
+msgstr "Unternehmenswährung"
+
+msgctxt "field:cashbook.book,company_currency_digits:"
+msgid "Currency Digits (Ref.)"
+msgstr "Nachkommastellen Währung (Ref.)"
+
##################
# cashbook.split #
diff --git a/locale/en.po b/locale/en.po
index 338f7cf..dcea347 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -518,6 +518,22 @@ msgctxt "field:cashbook.book,right:"
msgid "Right"
msgstr "Right"
+msgctxt "field:cashbook.book,balance_ref:"
+msgid "Balance (Ref.)"
+msgstr "Balance (Ref.)"
+
+msgctxt "help:cashbook.book,balance_ref:"
+msgid "Balance in company currency"
+msgstr "Balance in company currency"
+
+msgctxt "field:cashbook.book,company_currency:"
+msgid "Company Currency"
+msgstr "Company Currency"
+
+msgctxt "field:cashbook.book,company_currency_digits:"
+msgid "Currency Digits (Ref.)"
+msgstr "Currency Digits (Ref.)"
+
msgctxt "model:cashbook.split,name:"
msgid "Split booking line"
msgstr "Split booking line"
diff --git a/tests/test_book.py b/tests/test_book.py
index 67fea04..0183094 100644
--- a/tests/test_book.py
+++ b/tests/test_book.py
@@ -56,6 +56,110 @@ class BookTestCase(ModuleTestCase):
self.assertEqual(book.state, 'open')
self.assertEqual(book.state_string, 'Open')
+ @with_transaction()
+ def test_book_create_2nd_currency(self):
+ """ create cashbook, in 2nd currency, check balance-fields
+ """
+ pool = Pool()
+ Book = pool.get('cashbook.book')
+
+ types = self.prep_type()
+ company = self.prep_company()
+
+ # add EURO, set company-currency to EURO
+ (usd, euro) = self.prep_2nd_currency(company)
+ category = self.prep_category(cattype='in')
+ self.assertEqual(company.currency.rec_name, 'Euro')
+
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'btype': types.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, 5),
+ 'description': 'Amount in USD',
+ 'bookingtype': 'in',
+ 'category': category.id,
+ 'amount': Decimal('10.0'),
+ }])],
+ }])
+
+ with Transaction().set_context({
+ 'date': date(2022, 5, 5),
+ }):
+ self.assertEqual(book.rec_name, 'Book 1 | 10.00 usd | Open')
+ self.assertEqual(book.currency.rec_name, 'usd')
+ self.assertEqual(book.currency.rate, Decimal('1.05'))
+ self.assertEqual(book.company_currency.rec_name, 'Euro')
+ self.assertEqual(book.company_currency.rate, Decimal('1.0'))
+
+ self.assertEqual(book.balance, Decimal('10.0'))
+ self.assertEqual(book.balance_ref, Decimal('9.52'))
+
+ self.assertEqual(len(book.lines), 1)
+ self.assertEqual(book.lines[0].rec_name,
+ '05/05/2022|Rev|10.00 usd|Amount in USD [Cat1]')
+
+ @with_transaction()
+ def test_book_create_2nd_currency_hierarchical(self):
+ """ create cashbook-hierarchy, in 2nd currency,
+ check balance-fields
+ """
+ pool = Pool()
+ Book = pool.get('cashbook.book')
+
+ types = self.prep_type()
+ company = self.prep_company()
+
+ # add EURO, set company-currency to EURO
+ (usd, euro) = self.prep_2nd_currency(company)
+ category = self.prep_category(cattype='in')
+ self.assertEqual(company.currency.rec_name, 'Euro')
+
+ book, = Book.create([{
+ 'name': 'Book 1',
+ 'company': company.id,
+ 'currency': euro.id,
+ 'childs': [('create', [{
+ 'name': 'Book 2',
+ 'btype': types.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, 5),
+ 'description': 'Amount in USD',
+ 'bookingtype': 'in',
+ 'category': category.id,
+ 'amount': Decimal('10.0'),
+ }])],
+ }])],
+ }])
+
+ with Transaction().set_context({
+ 'date': date(2022, 5, 5),
+ }):
+ self.assertEqual(book.rec_name, 'Book 1')
+ self.assertEqual(book.currency.rec_name, 'Euro')
+ self.assertEqual(book.currency.rate, Decimal('1.0'))
+ self.assertEqual(book.company_currency, None)
+ self.assertEqual(book.balance, Decimal('9.52'))
+ self.assertEqual(book.balance_ref, None)
+ self.assertEqual(len(book.lines), 0)
+ self.assertEqual(len(book.childs), 1)
+
+ self.assertEqual(book.childs[0].rec_name, 'Book 1/Book 2 | 10.00 usd | Open')
+ self.assertEqual(book.childs[0].currency.rec_name, 'usd')
+ self.assertEqual(book.childs[0].currency.rate, Decimal('1.05'))
+ self.assertEqual(book.childs[0].company_currency.rec_name, 'Euro')
+ self.assertEqual(book.childs[0].balance, Decimal('10.0'))
+ self.assertEqual(book.childs[0].balance_ref, Decimal('9.52'))
+ self.assertEqual(len(book.childs[0].lines), 1)
+
@with_transaction()
def test_book_create_hierarchy(self):
""" create cashbook, hierarchical
diff --git a/tests/test_config.py b/tests/test_config.py
index 368ad10..7ff9105 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -98,6 +98,13 @@ class ConfigTestCase(ModuleTestCase):
'rate': Decimal('1.05'),
}])
+ # delete unwanted rates
+ usd_1 = CurrencyRate.search([
+ ('currency.id', '=', usd.id),
+ ('date', '!=', date(2022, 5, 2)),
+ ])
+ CurrencyRate.delete(usd_1)
+
return (usd, euro)
@with_transaction()
diff --git a/view/book_form.xml b/view/book_form.xml
index 24a27b5..7eec6b1 100644
--- a/view/book_form.xml
+++ b/view/book_form.xml
@@ -15,10 +15,14 @@ full copyright notices and license terms. -->
+
+
-
-
+
+
+
+
@@ -32,6 +36,11 @@ full copyright notices and license terms. -->
+
+
+
+
+
@@ -42,8 +51,6 @@ full copyright notices and license terms. -->
-
-
diff --git a/view/book_tree.xml b/view/book_tree.xml
index 32a14b6..3cefa9d 100644
--- a/view/book_tree.xml
+++ b/view/book_tree.xml
@@ -4,7 +4,7 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
-
+