book: quantity, umrechnung + tests

This commit is contained in:
Frederik Jaeckel 2022-12-23 00:34:45 +01:00
parent 71aa1de06f
commit 8f8da805d4
5 changed files with 369 additions and 8 deletions

112
book.py
View file

@ -5,9 +5,14 @@
from trytond.model import fields, SymbolMixin from trytond.model import fields, SymbolMixin
from trytond.exceptions import UserError from trytond.exceptions import UserError
from trytond.pool import PoolMeta from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Or, Len from trytond.pyson import Eval, Or, Len
from trytond.modules.cashbook.book import STATES2, DEPENDS2 from trytond.modules.cashbook.book import STATES2, DEPENDS2
from trytond.transaction import Transaction
from decimal import Decimal
from sql.functions import CurrentDate
from sql.aggregate import Sum
from sql.conditionals import Case, Coalesce
class Book(SymbolMixin, metaclass=PoolMeta): class Book(SymbolMixin, metaclass=PoolMeta):
@ -55,6 +60,32 @@ class Book(SymbolMixin, metaclass=PoolMeta):
}, depends=DEPENDS2+['feature', 'lines', 'asset_uomcat']) }, depends=DEPENDS2+['feature', 'lines', 'asset_uomcat'])
symbol = fields.Function(fields.Char(string='Symbol', readonly=True), symbol = fields.Function(fields.Char(string='Symbol', readonly=True),
'on_change_with_symbol') 'on_change_with_symbol')
quantity = fields.Function(fields.Numeric(string='Quantity',
help='Quantity of assets until to date', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature']),
'get_asset_quantity')
quantity_all = fields.Function(fields.Numeric(string='Total Quantity',
help='Total quantity of all assets', readonly=True,
digits=(16, Eval('quantity_digits', 4)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['quantity_digits', 'feature']),
'get_asset_quantity')
current_value = fields.Function(fields.Numeric(string='Value',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_asset_quantity')
current_value_ref = fields.Function(fields.Numeric(string='Value (Ref.)',
readonly=True, digits=(16, Eval('currency_digits', 2)),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'feature']),
'get_asset_quantity')
@fields.depends('asset', 'quantity_uom') @fields.depends('asset', 'quantity_uom')
def on_change_asset(self): def on_change_asset(self):
@ -69,6 +100,85 @@ class Book(SymbolMixin, metaclass=PoolMeta):
""" """
return 4 return 4
@classmethod
def get_asset_quantity(cls, cashbooks, names):
""" get quantities
"""
pool = Pool()
CBook = pool.get('cashbook.book')
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_line = Line.__table__()
tab_cur = Currency.__table__()
tab_asset = Asset.__table__()
(tab_rate, tab2) = Asset.get_rate_data_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_cur,
condition=tab_book.currency==tab_cur.id,
).join(tab_asset,
condition=tab_book.asset==tab_asset.id,
).join(tab_rate,
condition=tab_book.asset==tab_rate.id,
type_ = 'LEFT OUTER',
).select(
tab_book.id, # 0
Coalesce(Sum(Case(
(tab_line.date <= query_date, tab_line.quantity),
else_ = Decimal('0.0'),
)), Decimal('0.0')).as_('quantity'), # 1
Sum(tab_line.quantity).as_('quantity_all'), # 2
Coalesce(tab_rate.rate, Decimal('0.0')).as_('rate'), # 3
tab_book.currency, # 4
tab_cur.digits.as_('currency_digits'), # 5
tab_asset.uom, # 6
tab_book.quantity_uom, # 7
tab_asset.currency.as_('asset_currency'), #8
group_by=[tab_book.id, tab_rate.rate,
tab_book.currency, tab_cur.digits, tab_asset.uom,
tab_book.quantity_uom, tab_asset.currency],
)
cursor.execute(*query)
records = cursor.fetchall()
for record in records:
# uom-factor
if record[6] == record[7]:
uom_factor = Decimal('1.0')
else :
uom_factor = Decimal(
Uom.compute_qty(Uom(record[6]), 1.0, Uom(record[7]), round=False)
)
values = {
'quantity': record[1],
'quantity_all': record[2],
'current_value': Currency.compute(
record[8],
record[3] * record[1] / uom_factor,
record[4]
),
'current_value_ref': Currency.compute(
record[8],
record[3] * record[1] / uom_factor,
company_currency if company_currency is not None else record[8],
),
}
for name in names:
result[name][record[0]] = values[name]
return result
@fields.depends('quantity_uom', 'currency') @fields.depends('quantity_uom', 'currency')
def on_change_with_symbol(self, name=None): def on_change_with_symbol(self, name=None):
""" get symbol for asset """ get symbol for asset

View file

@ -18,6 +18,14 @@ msgctxt "view:cashbook.book:"
msgid "Asset" msgid "Asset"
msgstr "Vermögenswert" msgstr "Vermögenswert"
msgctxt "view:cashbook.book:"
msgid "Quantity"
msgstr "Anzahl"
msgctxt "view:cashbook.book:"
msgid "Current value of the asset"
msgstr "aktueller Wert des Vermögenswertes"
msgctxt "field:cashbook.book,asset:" msgctxt "field:cashbook.book,asset:"
msgid "Asset" msgid "Asset"
msgstr "Vermögenswert" msgstr "Vermögenswert"
@ -34,7 +42,7 @@ msgctxt "help:cashbook.book,asset_uomcat:"
msgid "UOM Category" msgid "UOM Category"
msgstr "Einheitenkategorie" msgstr "Einheitenkategorie"
msgctxt "help:cashbook.book,quantity_uom:" msgctxt "field:cashbook.book,quantity_uom:"
msgid "UOM" msgid "UOM"
msgstr "Einheit" msgstr "Einheit"
@ -42,6 +50,38 @@ msgctxt "field:cashbook.book,symbol:"
msgid "Symbol" msgid "Symbol"
msgstr "Symbol" msgstr "Symbol"
msgctxt "field:cashbook.book,quantity:"
msgid "Quantity"
msgstr "Anzahl"
msgctxt "help:cashbook.book,quantity:"
msgid "Quantity of assets until to date"
msgstr "Menge der Vermögenswerte bis heute"
msgctxt "field:cashbook.book,quantity_all:"
msgid "Total Quantity"
msgstr "Gesamtanzahl"
msgctxt "help:cashbook.book,quantity_all:"
msgid "Total quantity of all assets"
msgstr "Gesamtmenge der Vermögenswerte"
msgctxt "field:cashbook.book,current_value:"
msgid "Value"
msgstr "Wert"
msgctxt "help:cashbook.book,current_value:"
msgid "Current Value in cashbook currency"
msgstr "aktueller Wert in der Kassenbuchwährung"
msgctxt "field:cashbook.book,current_value_ref:"
msgid "Value (Ref)"
msgstr "Wert (Ref)"
msgctxt "help:cashbook.book,current_value:"
msgid "Current Value in company currency"
msgstr "aktueller Wert in der Unternehmenswährung"
################# #################
# cashbook.line # # cashbook.line #

View file

@ -10,6 +10,14 @@ msgctxt "view:cashbook.book:"
msgid "Asset" msgid "Asset"
msgstr "Asset" msgstr "Asset"
msgctxt "view:cashbook.book:"
msgid "Quantity"
msgstr "Quantity"
msgctxt "view:cashbook.book:"
msgid "Value of the asset"
msgstr "Value of the asset"
msgctxt "field:cashbook.book,asset:" msgctxt "field:cashbook.book,asset:"
msgid "Asset" msgid "Asset"
msgstr "Asset" msgstr "Asset"
@ -26,7 +34,7 @@ msgctxt "help:cashbook.book,asset_uomcat:"
msgid "UOM Category" msgid "UOM Category"
msgstr "UOM Category" msgstr "UOM Category"
msgctxt "help:cashbook.book,quantity_uom:" msgctxt "field:cashbook.book,quantity_uom:"
msgid "UOM" msgid "UOM"
msgstr "UOM" msgstr "UOM"
@ -34,6 +42,38 @@ msgctxt "field:cashbook.book,symbol:"
msgid "Symbol" msgid "Symbol"
msgstr "Symbol" msgstr "Symbol"
msgctxt "field:cashbook.book,quantity:"
msgid "Quantity"
msgstr "Quantity"
msgctxt "help:cashbook.book,quantity:"
msgid "Quantity of assets until to date"
msgstr "Quantity of assets until to date"
msgctxt "field:cashbook.book,quantity_all:"
msgid "Total Quantity"
msgstr "Total Quantity"
msgctxt "help:cashbook.book,quantity_all:"
msgid "Total quantity of all assets"
msgstr "Total quantity of all assets"
msgctxt "field:cashbook.book,current_value:"
msgid "Value"
msgstr "Value"
msgctxt "help:cashbook.book,current_value:"
msgid "Current Value in cashbook currency"
msgstr "Current Value in cashbook currency"
msgctxt "field:cashbook.book,current_value_ref:"
msgid "Value (Ref)"
msgstr "Value (Ref)"
msgctxt "help:cashbook.book,current_value:"
msgid "Current Value in company currency"
msgstr "Current Value in company currency"
msgctxt "field:cashbook.line,quantity_digits:" msgctxt "field:cashbook.line,quantity_digits:"
msgid "Digits" msgid "Digits"
msgstr "Digits" msgstr "Digits"

View file

@ -56,6 +56,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
pool = Pool() pool = Pool()
Book = pool.get('cashbook.book') Book = pool.get('cashbook.book')
BType = pool.get('cashbook.type') BType = pool.get('cashbook.type')
Asset = pool.get('investment.asset')
types = self.prep_type() types = self.prep_type()
BType.write(*[ BType.write(*[
@ -70,6 +71,22 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
asset = self.prep_asset_item( asset = self.prep_asset_item(
company=company, company=company,
product = self.prep_asset_product(name='Product 1')) product = self.prep_asset_product(name='Product 1'))
Asset.write(*[
[asset],
{
'rates': [('create', [{
'date': date(2022, 5, 1),
'rate': Decimal('2.5'),
}, {
'date': date(2022, 5, 2),
'rate': Decimal('2.8'),
}])],
}])
self.assertEqual(asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022')
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
self.assertEqual(asset.symbol, 'usd/u') self.assertEqual(asset.symbol, 'usd/u')
book, = Book.create([{ book, = Book.create([{
@ -77,7 +94,7 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
'name': 'Book 1', 'name': 'Book 1',
'btype': types.id, 'btype': types.id,
'company': company.id, 'company': company.id,
'currency': company.currency.id, 'currency': euro.id,
'number_sequ': self.prep_sequence().id, 'number_sequ': self.prep_sequence().id,
'asset': asset.id, 'asset': asset.id,
'quantity_uom': asset.uom.id, 'quantity_uom': asset.uom.id,
@ -90,22 +107,161 @@ class CbInvTestCase(CashbookTestCase, InvestmentTestCase):
'amount': Decimal('2.5'), 'amount': Decimal('2.5'),
'party': party.id, 'party': party.id,
'quantity': Decimal('1.453'), 'quantity': Decimal('1.453'),
}, {
'date': date(2022, 5, 10),
'description': 'Text 2',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('4.0'),
'party': party.id,
'quantity': Decimal('3.3'),
}], }],
)], )],
}]) }])
self.assertEqual(book.name, 'Book 1') self.assertEqual(book.name, 'Book 1')
self.assertEqual(book.rec_name, 'Book 1 | 2.50 usd | Open') self.assertEqual(book.rec_name, 'Book 1 | 6.50 € | Open')
self.assertEqual(book.state, 'open') self.assertEqual(book.state, 'open')
self.assertEqual(book.feature, 'asset') self.assertEqual(book.feature, 'asset')
self.assertEqual(book.quantity_digits, 3) self.assertEqual(book.quantity_digits, 3)
self.assertEqual(book.balance_all, Decimal('2.5')) self.assertEqual(book.balance_all, Decimal('6.5'))
self.assertEqual(len(book.lines), 1) self.assertEqual(len(book.lines), 2)
self.assertEqual(book.lines[0].amount, Decimal('2.5')) self.assertEqual(book.lines[0].amount, Decimal('2.5'))
self.assertEqual(book.lines[0].quantity, Decimal('1.453')) self.assertEqual(book.lines[0].quantity, Decimal('1.453'))
self.assertEqual(book.lines[0].quantity_digits, 3) self.assertEqual(book.lines[0].quantity_digits, 3)
self.assertEqual(book.lines[0].quantity_uom.symbol, 'u') self.assertEqual(book.lines[0].quantity_uom.symbol, 'u')
self.assertEqual(book.symbol, 'usd/u')
self.assertEqual(book.lines[1].amount, Decimal('4.0'))
self.assertEqual(book.lines[1].quantity, Decimal('3.3'))
self.assertEqual(book.lines[1].quantity_digits, 3)
self.assertEqual(book.lines[1].quantity_uom.symbol, 'u')
self.assertEqual(book.symbol, '€/u')
self.assertEqual(book.asset.rec_name, 'Product 1 | 2.8000 usd/u | 05/02/2022')
# check quantities at cashbook
with Transaction().set_context({
'qdate': date(2022, 5, 5),
'company': company.id,
}):
book2, = Book.browse([book])
self.assertEqual(book.asset.rate, Decimal('2.8')) # usd
self.assertEqual(book2.quantity, Decimal('1.453'))
self.assertEqual(book2.quantity_all, Decimal('4.753'))
# 2.8 / 1.05 * 1.453 = 3.87466
self.assertEqual(book2.current_value, Decimal('3.87'))
self.assertEqual(book2.current_value_ref, Decimal('3.87'))
with Transaction().set_context({
'qdate': date(2022, 5, 12),
'company': company.id,
}):
book2, = Book.browse([book])
self.assertEqual(book2.quantity, Decimal('4.753'))
self.assertEqual(book2.quantity_all, Decimal('4.753'))
# 2.8 / 1.05 * 4.753 = 12.67466
self.assertEqual(book2.current_value, Decimal('12.67'))
self.assertEqual(book2.current_value_ref, Decimal('12.67'))
@with_transaction()
def test_assetbook_check_uom_and_currency_convert(self):
""" asset in US$/Ounce, cashbook in EUR/Gram
"""
pool = Pool()
Book = pool.get('cashbook.book')
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'))
# set product to ounce
ounce, = Uom.search([('symbol', '=', 'oz')])
gram, = Uom.search([('symbol', '=', 'g')])
ProdTempl.write(*[
[asset.product.template],
{
'default_uom': ounce.id,
'name': 'Aurum',
}])
Asset.write(*[
[asset],
{
'uom': ounce.id,
'rates': [('create', [{
'date': date(2022, 5, 1),
'rate': Decimal('1750.0'),
}, ])],
}])
self.assertEqual(asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022')
(usd, euro) = self.prep_2nd_currency(company)
self.assertEqual(company.currency.rec_name, 'Euro')
self.assertEqual(asset.symbol, 'usd/oz')
book, = Book.create([{
'start_date': date(2022, 4, 1),
'name': 'Aurum-Storage',
'btype': types.id,
'company': company.id,
'currency': euro.id,
'number_sequ': self.prep_sequence().id,
'asset': asset.id,
'quantity_uom': gram.id,
'quantity_digits': 3,
'lines': [('create', [{
'date': date(2022, 5, 1),
'description': 'store some metal',
'category': category.id,
'bookingtype': 'in',
'amount': Decimal('1250.0'),
'party': party.id,
'quantity': Decimal('20.0'),
}],
)],
}])
self.assertEqual(book.rec_name, 'Aurum-Storage | 1,250.00 € | Open')
self.assertEqual(book.balance_all, Decimal('1250.0'))
self.assertEqual(len(book.lines), 1)
self.assertEqual(book.lines[0].amount, Decimal('1250.0'))
self.assertEqual(book.lines[0].quantity, Decimal('20.0'))
self.assertEqual(book.lines[0].quantity_uom.symbol, 'g')
self.assertEqual(book.symbol, '€/g')
self.assertEqual(book.asset.rec_name, 'Aurum | 1,750.0000 usd/oz | 05/01/2022')
# check quantities at cashbook
with Transaction().set_context({
'qdate': date(2022, 5, 1),
'company': company.id,
}):
book2, = Book.browse([book])
self.assertEqual(book.asset.rate, Decimal('1750.0')) # usd
self.assertEqual(book2.quantity, Decimal('20.0'))
self.assertEqual(book2.quantity_all, Decimal('20.0'))
# usd --> eur: 1750 / 1.05 = 1666.666
# 1 ounce --> 20 gram: 1666.666 * 20 / 28.3495 = 1175.7996
# bette we use 'Troy Ounce': 1 oz.tr. = 31.1034768 gram
self.assertEqual(book2.current_value, Decimal('1175.80'))
self.assertEqual(book2.current_value_ref, Decimal('1175.80'))
@with_transaction() @with_transaction()
def test_assetbook_book_uom(self): def test_assetbook_book_uom(self):

View file

@ -4,6 +4,21 @@ The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. --> full copyright notices and license terms. -->
<data> <data>
<xpath expr="/form/notebook/page[@name='balance']/field[@name='balance_ref']"
position="after">
<separator colspan="4" name="quantity" string="Quantity"/>
<label name="quantity"/>
<field name="quantity" symbol="quantity_uom"/>
<label name="quantity_all"/>
<field name="quantity_all" symbol="quantity_uom"/>
<separator colspan="4" name="current_value" string="Current value of the asset"/>
<label name="current_value"/>
<field name="current_value" symbol="currency"/>
<label name="current_value_ref"/>
<field name="current_value_ref" symbol="company_currency"/>
</xpath>
<xpath expr="/form/notebook/page[@id='pggeneral']" position="after"> <xpath expr="/form/notebook/page[@id='pggeneral']" position="after">
<page name="asset" string="Asset"> <page name="asset" string="Asset">
<label name="asset"/> <label name="asset"/>