cashbook_investment/line.py
Frederik Jaeckel f86db6dea3 line: quantity_balance, validate,
reconciliation: start/end-quantity
todo: tests
2022-12-31 16:15:23 +01:00

188 lines
6.9 KiB
Python

# -*- 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 decimal import Decimal
from trytond.model import fields
from trytond.pool import PoolMeta
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
STATESQ = {
'required': Or(
Eval('feature', '') == 'asset',
Eval('booktransf_feature', '') == 'asset',
),
'invisible': ~Or(
Eval('feature', '') == 'asset',
Eval('booktransf_feature', '') == 'asset',
),
'readonly': Or(
STATES['readonly'],
Eval('bookingtype', '').in_(['spin', 'spout']),
),
}
DEPENDSQ = DEPENDS+['feature', 'booktransf_feature',
'quantity_digits', 'bookingtype']
class Line(metaclass=PoolMeta):
__name__ = 'cashbook.line'
quantity = fields.Numeric(string='Quantity',
digits=(16, Eval('quantity_digits', 4)),
states=STATESQ, depends=DEPENDSQ)
quantity_credit = fields.Numeric(string='Quantity Credit',
digits=(16, Eval('quantity_digits', 4)), readonly=True,
states={
'invisible': STATESQ['invisible'],
'required': STATESQ['required'],
}, depends=DEPENDSQ)
quantity_debit = fields.Numeric(string='Quantity Debit',
digits=(16, Eval('quantity_digits', 4)), readonly=True,
states={
'invisible': STATESQ['invisible'],
'required': STATESQ['required'],
}, depends=DEPENDSQ)
quantity_digits = fields.Function(fields.Integer(string='Digits',
readonly=True, states={'invisible': True}),
'on_change_with_quantity_digits')
quantity_uom = fields.Function(fields.Many2One(string='Symbol',
readonly=True, model_name='product.uom'),
'on_change_with_quantity_uom')
asset_rate = fields.Function(fields.Numeric(string='Rate',
readonly=True,
digits=(16, If(
Eval('currency_digits', 2) > Eval('quantity_digits', 2),
Eval('currency_digits', 2), Eval('quantity_digits', 2))),
states={
'invisible': Eval('feature', '') != 'asset',
}, depends=['currency_digits', 'quantity_digits', 'feature']),
'on_change_with_asset_rate')
quantity_balance = fields.Function(fields.Numeric(string='Quantity',
digits=(16, Eval('quantity_digits', 4)),
help='Number of shares in the cashbook up to the current row if the default sort applies.',
readonly=True, depends=['quantity_digits']),
'on_change_with_quantity_balance')
@classmethod
def get_debit_credit(cls, values):
""" compute quantity_debit/quantity_credit from quantity
"""
values2 = super(Line, cls).get_debit_credit(values)
if isinstance(values, dict):
type_ = values.get('bookingtype', None)
quantity = values.get('quantity', None)
else :
type_ = getattr(values, 'bookingtype', None)
quantity = getattr(values, 'quantity', None)
if type_:
if quantity is not None:
if type_ in ['in', 'mvin', 'spin']:
values2.update({
'quantity_debit': Decimal('0.0'),
'quantity_credit': quantity,
})
elif type_ in ['out', 'mvout', 'spout']:
values2.update({
'quantity_debit': quantity,
'quantity_credit': Decimal('0.0'),
})
else :
raise ValueError('invalid "bookingtype"')
return values2
@classmethod
def get_counterpart_values(cls, line, values={}):
""" add quantity to counterpart
"""
result = super(Line, cls).get_counterpart_values(line, values)
result['quantity'] = line.quantity
return result
@fields.depends('quantity', 'amount', 'currency_digits', 'quantity_digits')
def on_change_with_asset_rate(self, name=None):
""" get rate
"""
if (self.quantity is None) or (self.amount is None):
return
digit = max(
self.currency_digits if self.currency_digits is not None else 2,
self.quantity_digits if self.quantity_digits is not None else 4)
if self.quantity != Decimal('0.0'):
return (
self.amount / self.quantity
).quantize(Decimal(str(1/10**digit)))
@fields.depends('cashbook', '_parent_cashbook.quantity_uom')
def on_change_with_quantity_uom(self, name=None):
""" get quantity-unit of asset
"""
if self.cashbook:
if self.cashbook.quantity_uom:
return self.cashbook.quantity_uom.id
@fields.depends('cashbook', '_parent_cashbook.quantity_digits')
def on_change_with_quantity_digits(self, name=None):
""" get digits from cashbook
"""
if self.cashbook:
return self.cashbook.quantity_digits
return 4
@classmethod
def validate(cls, lines):
""" deny pos/neg mismatch
"""
super(Line, cls).validate(lines)
for line in lines:
# ignore non-asset-lines
if line.cashbook.feature != 'asset':
continue
# quantity must be set
if (line.quantity is None) or \
(line.quantity_credit is None) or \
(line.quantity_debit is None):
raise UserError(gettext(
'cashbook_investment.msg_line_quantity_not_set',
linetxt = line.rec_name,
))
# quantity and amount must with same sign
(amount_sign, a_dig, a_exp) = line.amount.as_tuple()
(quantity_sign, q_dig, q_exp) = line.quantity.as_tuple()
if amount_sign != quantity_sign:
raise UserError(gettext(
'cashbook_investment.msg_line_sign_mismatch',
linetxt = line.rec_name,
))
@classmethod
def write(cls, *args):
""" add or update quanity_debit/credit
"""
actions = iter(args)
to_write = []
for lines, values in zip(actions, actions):
# update debit / credit
if len(set(values.keys()).intersection(set({'quantity', 'bookingtype'}))) > 0:
for line in lines:
values2 = {}
values2.update(values)
values2.update(cls.get_debit_credit({
x:values.get(x, getattr(line, x)) for x in ['quantity', 'bookingtype']
}))
to_write.extend([lines, values2])
else :
to_write.extend([lines, values])
super(Line, cls).write(*to_write)
# end LineContext