add worker-based precalculation of cashbook-values

This commit is contained in:
Frederik Jaeckel 2023-12-27 15:49:02 +01:00
parent 9ef465f40f
commit 5d8f924960
17 changed files with 1060 additions and 98 deletions

View file

@ -1,59 +0,0 @@
# -*- 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.tests.test_tryton import with_transaction
from trytond.pool import Pool
from datetime import date
from decimal import Decimal
class CurrencyTestCase(object):
""" test currency
"""
@with_transaction()
def test_currency_update_cache(self):
""" add/update/del rate of currency, check cache
"""
pool = Pool()
Currency = pool.get('currency.currency')
CurrencyRate = pool.get('currency.currency.rate')
self.prep_config()
self.prep_company()
# TODO: check update of cashbook if currency changes
currency, = Currency.search([('name', '=', 'usd')])
CurrencyRate.delete(currency.rates)
self.assertEqual(len(currency.rates), 0)
# add rate
Currency.write(*[
[currency],
{
'rates': [('create', [{
'date': date(2022, 5, 1),
'rate': Decimal('1.05'),
}])],
}])
self.assertEqual(len(currency.rates), 1)
Currency.write(*[
[currency],
{
'rates': [
('write', [currency.rates[0].id], {
'rate': Decimal('1.06'),
})],
}])
self.assertEqual(len(currency.rates), 1)
Currency.write(*[
[currency],
{
'rates': [('delete', [currency.rates[0].id])],
}])
# end CurrencyTestCase

View file

@ -291,6 +291,8 @@ class LineTestCase(object):
self.assertEqual(books[0].lines[0].reference, None)
self.assertEqual(len(books[0].lines[0].references), 1)
self.prep_valstore_run_worker()
self.assertEqual(
books[0].lines[0].rec_name,
'05/05/2022|to|-10.00 usd|Transfer USD --> ' +
@ -366,6 +368,8 @@ class LineTestCase(object):
self.assertEqual(books[0].lines[0].reference, None)
self.assertEqual(len(books[0].lines[0].references), 1)
self.prep_valstore_run_worker()
self.assertEqual(
books[0].lines[0].rec_name,
'05/05/2022|from|10.00 usd|Transfer USD <-- ' +
@ -458,6 +462,8 @@ class LineTestCase(object):
self.assertEqual(books[0].lines[0].reference, None)
self.assertEqual(len(books[0].lines[0].references), 1)
self.prep_valstore_run_worker()
# 10 CHF --> USD: USD = CHF * 1.05 / 1.04
# 10 CHF = 10.0961538 USD
# EUR | USD | CHF
@ -1147,6 +1153,8 @@ class LineTestCase(object):
# set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual(len(book.lines), 1)
self.assertEqual(
book.lines[0].rec_name,
@ -1216,6 +1224,8 @@ class LineTestCase(object):
# set line to 'checked', this creates the counterpart
Line.wfcheck(list(book.lines))
self.prep_valstore_run_worker()
self.assertEqual(len(book.lines), 1)
self.assertEqual(
book.lines[0].rec_name,

View file

@ -14,11 +14,11 @@ from .config import ConfigTestCase
from .category import CategoryTestCase
from .reconciliation import ReconTestCase
from .bookingwiz import BookingWizardTestCase
from .currency import CurrencyTestCase
from .valuestore import ValuestoreTestCase
class CashbookTestCase(
CurrencyTestCase,
ValuestoreTestCase,
BookingWizardTestCase,
ReconTestCase,
CategoryTestCase,

610
tests/valuestore.py Normal file
View file

@ -0,0 +1,610 @@
# -*- 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 datetime import date, timedelta
from decimal import Decimal
from trytond.tests.test_tryton import with_transaction
from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond.transaction import Transaction
class ValuestoreTestCase(object):
""" test storage of values
"""
@with_transaction()
def test_valstore_update_currency_rate(self):
""" create cashbook, check update of cashbook on
update of rates of currency
"""
pool = Pool()
Book = pool.get('cashbook.book')
ValueStore = pool.get('cashbook.values')
Currency = pool.get('currency.currency')
Queue = pool.get('ir.queue')
types = self.prep_type()
company = self.prep_company()
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
with Transaction().set_context({
'company': company.id,
'date': date(2022, 5, 20)}):
self.assertEqual(Queue.search_count([]), 0)
category = self.prep_category(cattype='in')
party = self.prep_party()
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, 1),
'description': '10 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('10.0'),
'party': party.id,
}, {
'date': date(2022, 5, 10),
'description': '5 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('5.0'),
'party': party.id,
}])],
}])
# run worker
self.assertEqual(ValueStore.search_count([]), 3)
self.prep_valstore_run_worker()
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 15.00 usd | Open')
self.assertEqual(book.balance, Decimal('15.0'))
self.assertEqual(book.balance_all, Decimal('15.0'))
self.assertEqual(book.balance_ref, Decimal('14.29'))
self.assertEqual(len(book.value_store), 3)
self.assertEqual(
book.value_store[0].rec_name,
'[Book 1 | 15.00 usd | Open]|balance|15.00|2')
self.assertEqual(
book.value_store[1].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
book.value_store[2].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_ref|14.29|2')
# add rate to usd
self.assertEqual(Queue.search_count([]), 0)
Currency.write(*[
[usd],
{
'rates': [('create', [{
'date': date(2022, 5, 6),
'rate': Decimal('1.08'),
}])],
}])
self.assertEqual(Queue.search_count([]), 1)
self.prep_valstore_run_worker()
self.assertEqual(Queue.search_count([]), 0)
# check reference-currency
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 15.00 usd | Open')
self.assertEqual(book.balance, Decimal('15.0'))
self.assertEqual(book.balance_all, Decimal('15.0'))
self.assertEqual(book.balance_ref, Decimal('13.89'))
self.assertEqual(len(book.value_store), 3)
self.assertEqual(
book.value_store[0].rec_name,
'[Book 1 | 15.00 usd | Open]|balance|15.00|2')
self.assertEqual(
book.value_store[1].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
book.value_store[2].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_ref|13.89|2')
# find rate
self.assertEqual(len(usd.rates), 2)
self.assertEqual(usd.rates[0].date, date(2022, 5, 6))
self.assertEqual(usd.rates[0].rate, Decimal('1.08'))
# update rate
self.assertEqual(Queue.search_count([]), 0)
Currency.write(*[
[usd],
{
'rates': [(
'write',
[usd.rates[0]],
{'rate': Decimal('1.12')})],
}])
self.assertEqual(Queue.search_count([]), 1)
self.prep_valstore_run_worker()
self.assertEqual(Queue.search_count([]), 0)
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 15.00 usd | Open')
self.assertEqual(book.balance_ref, Decimal('13.39'))
self.assertEqual(
book.value_store[2].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_ref|13.39|2')
# delete rate
self.assertEqual(Queue.search_count([]), 0)
Currency.write(*[
[usd],
{
'rates': [('delete', [usd.rates[0]])],
}])
self.assertEqual(Queue.search_count([]), 1)
self.prep_valstore_run_worker()
self.assertEqual(Queue.search_count([]), 0)
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 15.00 usd | Open')
self.assertEqual(book.balance_ref, Decimal('14.29'))
self.assertEqual(
book.value_store[2].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_ref|14.29|2')
def prep_valstore_run_worker(self):
""" run tasks from queue
"""
Queue = Pool().get('ir.queue')
tasks = Queue.search([])
for task in tasks:
task.run()
Queue.delete(tasks)
@with_transaction()
def test_valstore_update_store_values(self):
""" create cashbook, store value
"""
pool = Pool()
Book = pool.get('cashbook.book')
ValueStore = pool.get('cashbook.values')
Queue = pool.get('ir.queue')
types = self.prep_type()
company = self.prep_company()
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
with Transaction().set_context({'company': company.id}):
self.assertEqual(Queue.search_count([]), 0)
category = self.prep_category(cattype='in')
party = self.prep_party()
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, 1),
'description': '10 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('10.0'),
'party': party.id,
}, {
'date': date(2022, 5, 10),
'description': '5 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('5.0'),
'party': party.id,
}])],
}])
# run worker
self.assertEqual(ValueStore.search_count([]), 3)
self.prep_valstore_run_worker()
# check values until 2022-05-05
with Transaction().set_context({
'date': date(2022, 5, 5)}):
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 10.00 usd | Open')
self.assertEqual(book.balance, Decimal('10.0'))
self.assertEqual(book.balance_all, Decimal('15.0'))
self.assertEqual(book.balance_ref, Decimal('14.29'))
self.assertEqual(len(book.value_store), 3)
self.assertEqual(
book.value_store[0].rec_name,
'[Book 1 | 10.00 usd | Open]|balance|15.00|2')
self.assertEqual(
book.value_store[1].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
book.value_store[2].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_ref|14.29|2')
# values created by book-create, without context
self.assertEqual(ValueStore.search_count([]), 3)
values = ValueStore.search([], order=[('field_name', 'ASC')])
self.assertEqual(len(values), 3)
self.assertEqual(
values[0].rec_name,
'[Book 1 | 10.00 usd | Open]|balance|15.00|2')
self.assertEqual(
values[1].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
values[2].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_ref|14.29|2')
# check write of too much digits
self.assertRaisesRegex(
UserError,
r'The number of digits in the value ' +
r'"' + r"'12.345'" +
r'" for field "Value" in "[Book 1 | 10\.00 usd | ' +
r'Open]|balance|12\.35|2" of "Value Store" exceeds ' +
r'the limit of "2".',
ValueStore.write,
*[
[values[0]],
{
'numvalue': Decimal('12.345'),
}
])
# update with context
Book.valuestore_update_records([book])
values = ValueStore.search([], order=[('field_name', 'ASC')])
self.assertEqual(len(values), 3)
self.assertEqual(
values[0].rec_name,
'[Book 1 | 10.00 usd | Open]|balance|10.00|2')
self.assertEqual(
values[1].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
values[2].rec_name,
'[Book 1 | 10.00 usd | Open]|balance_ref|14.29|2')
# check values until 2022-05-15
with Transaction().set_context({
'date': date(2022, 5, 15)}):
book, = Book.search([])
self.assertEqual(book.rec_name, 'Book 1 | 15.00 usd | Open')
self.assertEqual(book.balance, Decimal('15.0'))
self.assertEqual(book.balance_all, Decimal('15.0'))
self.assertEqual(book.balance_ref, Decimal('14.29'))
# update values
self.assertEqual(ValueStore.search_count([]), 3)
Book.valuestore_update_records([book])
values = ValueStore.search([], order=[('field_name', 'ASC')])
self.assertEqual(len(values), 3)
self.assertEqual(
values[0].rec_name,
'[Book 1 | 15.00 usd | Open]|balance|15.00|2')
self.assertEqual(
values[1].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_all|15.00|2')
self.assertEqual(
values[2].rec_name,
'[Book 1 | 15.00 usd | Open]|balance_ref|14.29|2')
# delete book, should delete values
Book.write(*[
[book],
{'lines': [('delete', [x.id for x in book.lines])]}
])
self.assertEqual(ValueStore.search_count([]), 3)
Book.delete([book])
self.assertEqual(ValueStore.search_count([]), 0)
@with_transaction()
def test_valstore_update_store_values_line(self):
""" create cashbooks hierarchical, add lines
check update of parent cashbooks
"""
pool = Pool()
Book = pool.get('cashbook.book')
Line = pool.get('cashbook.line')
ValueStore = pool.get('cashbook.values')
types = self.prep_type()
company = self.prep_company()
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
with Transaction().set_context({'company': company.id}):
category = self.prep_category(cattype='in')
party = self.prep_party()
book, = Book.create([{
'name': 'Lev 0',
'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, 1),
'description': '10 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('10.0'),
'party': party.id,
}])],
'childs': [('create', [{
'name': 'Lev 1a',
'btype': types.id,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}, {
'name': 'Lev 1b',
'btype': types.id,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'start_date': date(2022, 5, 1),
}])],
}])
self.assertEqual(book.rec_name, 'Lev 0 | 10.00 usd | Open')
self.assertEqual(len(book.lines), 1)
self.assertEqual(
book.lines[0].rec_name,
'05/01/2022|Rev|10.00 usd|10 US$ [Cat1]')
self.assertEqual(len(book.childs), 2)
self.assertEqual(
book.childs[0].rec_name, 'Lev 0/Lev 1a | 0.00 € | Open')
self.assertEqual(len(book.childs[0].lines), 0)
self.assertEqual(
book.childs[1].rec_name, 'Lev 0/Lev 1b | 0.00 € | Open')
self.assertEqual(len(book.childs[1].lines), 0)
self.assertEqual(ValueStore.search_count([]), 9)
self.prep_valstore_run_worker()
values = ValueStore.search(
[], order=[('cashbook', 'ASC'), ('field_name', 'ASC')])
self.assertEqual(len(values), 9)
self.assertEqual(
values[0].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance|10.00|2')
self.assertEqual(
values[1].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance_all|10.00|2')
self.assertEqual(
values[2].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance_ref|9.52|2')
self.assertEqual(
values[3].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[4].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[5].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance_ref|0.00|2')
self.assertEqual(
values[6].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[7].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[8].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_ref|0.00|2')
# add bookings
Line.create([{
'cashbook': values[0].cashbook.id, # Lev 0
'amount': Decimal('2.0'),
'bookingtype': 'in',
'category': category.id,
'date': date(2022, 5, 10),
}, {
'cashbook': values[3].cashbook.id, # Lev 1a
'amount': Decimal('3.0'),
'bookingtype': 'in',
'category': category.id,
'date': date(2022, 5, 10),
}])
# add 'date' to context, will return computed
# (not stored) values
with Transaction().set_context({'date': date(2022, 5, 10)}):
values = ValueStore.search(
[], order=[('cashbook', 'ASC'), ('field_name', 'ASC')])
self.assertEqual(len(values), 9)
self.assertEqual(
values[0].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance|10.00|2')
self.assertEqual(
values[1].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance_all|10.00|2')
self.assertEqual(
values[2].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance_ref|9.52|2')
self.assertEqual(
values[3].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[4].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[5].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance_ref|0.00|2')
self.assertEqual(
values[6].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[7].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[8].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_ref|0.00|2')
# before run of workers - w/o 'date' in context
values = ValueStore.search(
[], order=[('cashbook', 'ASC'), ('field_name', 'ASC')])
self.assertEqual(len(values), 9)
self.assertEqual(
values[0].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance|10.00|2')
self.assertEqual(
values[1].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance_all|10.00|2')
self.assertEqual(
values[2].rec_name,
'[Lev 0 | 10.00 usd | Open]|balance_ref|9.52|2')
self.assertEqual(
values[3].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[4].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[5].rec_name,
'[Lev 0/Lev 1a | 0.00 € | Open]|balance_ref|0.00|2')
self.assertEqual(
values[6].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[7].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[8].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_ref|0.00|2')
self.prep_valstore_run_worker()
# after run of workers
values = ValueStore.search(
[], order=[('cashbook', 'ASC'), ('field_name', 'ASC')])
self.assertEqual(len(values), 9)
self.assertEqual(
values[0].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance|15.15|2')
self.assertEqual(
values[1].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance_all|15.15|2')
self.assertEqual(
values[2].rec_name,
'[Lev 0 | 15.15 usd | Open]|balance_ref|14.43|2')
self.assertEqual(
values[3].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance|3.00|2')
self.assertEqual(
values[4].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance_all|3.00|2')
self.assertEqual(
values[5].rec_name,
'[Lev 0/Lev 1a | 3.00 € | Open]|balance_ref|3.00|2')
self.assertEqual(
values[6].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance|0.00|2')
self.assertEqual(
values[7].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_all|0.00|2')
self.assertEqual(
values[8].rec_name,
'[Lev 0/Lev 1b | 0.00 € | Open]|balance_ref|0.00|2')
@with_transaction()
def test_valstore_maintain_values(self):
""" create cashbook, check maintenance -
update records by cron, delete lost records
"""
pool = Pool()
Book = pool.get('cashbook.book')
ValueStore = pool.get('cashbook.values')
tab_book = Book.__table__()
cursor = Transaction().connection.cursor()
types = self.prep_type()
company = self.prep_company()
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
category = self.prep_category(cattype='in')
party = self.prep_party()
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, 1),
'description': '10 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('10.0'),
'party': party.id,
}, {
'date': date(2022, 5, 10),
'description': '5 US$',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('5.0'),
'party': party.id,
}])],
}])
# clean 'numvalue'
# maintenance_values() will restore it
val1, = ValueStore.search([('field_name', '=', 'balance')])
ValueStore.write(*[[val1], {'numvalue': None}])
self.assertTrue(val1.write_date is not None)
self.assertTrue(val1.create_date is not None)
self.assertEqual(val1.numvalue, None)
# update outdated records
with Transaction().set_context({
'maintenance_date': val1.write_date.date() +
timedelta(days=2)}):
ValueStore.maintenance_values()
val1, = ValueStore.search([('field_name', '=', 'balance')])
self.assertEqual(val1.numvalue, Decimal('15.0'))
# delete book
self.assertEqual(Book.search_count([]), 1)
self.assertEqual(ValueStore.search_count([]), 3)
query = tab_book.delete(where=tab_book.id == book.id)
cursor.execute(*query)
self.assertEqual(Book.search_count([]), 0)
self.assertEqual(ValueStore.search_count([]), 0)
# end ValuestoreTestCase