ITSA points 28.06
This commit is contained in:
Binary file not shown.
@@ -96,6 +96,12 @@ def register():
|
||||
pricing.ImportPricesResult,
|
||||
duplicate.TradeCustomDuplicateStart,
|
||||
module='purchase_trade', type_='model')
|
||||
Pool.register(
|
||||
configuration.PurchaseConfiguration,
|
||||
module='purchase', type_='model')
|
||||
Pool.register(
|
||||
configuration.SaleConfiguration,
|
||||
module='sale', type_='model')
|
||||
Pool.register(
|
||||
incoming.ImportSwift,
|
||||
incoming.PrepareDocuments,
|
||||
|
||||
@@ -41,6 +41,23 @@ def default_itsa_currency():
|
||||
return record_id(currency) if currency else None
|
||||
|
||||
|
||||
def default_itsa_price_fix_type():
|
||||
fix_type = _search_first('price.fixtype', [
|
||||
[('name', '=', 'Market Price')],
|
||||
[('name', '=', 'Market price')],
|
||||
[('name', 'ilike', 'Market Price')],
|
||||
])
|
||||
return record_id(fix_type) if fix_type else None
|
||||
|
||||
|
||||
def default_itsa_price_calendar():
|
||||
calendar = _search_first('price.calendar', [
|
||||
[('name', '=', 'Argus Sulphuric Acid')],
|
||||
[('name', 'ilike', 'Argus Sulphuric Acid')],
|
||||
])
|
||||
return record_id(calendar) if calendar else None
|
||||
|
||||
|
||||
def default_itsa_unit():
|
||||
unit = _search_first('product.uom', [
|
||||
[('symbol', '=', 'Mt')],
|
||||
|
||||
@@ -4,6 +4,28 @@ from trytond.pyson import Eval
|
||||
from trytond.transaction import Transaction
|
||||
|
||||
|
||||
class PurchaseConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'purchase.configuration'
|
||||
|
||||
allow_modification_after_validation = fields.Boolean(
|
||||
"Autorise modification after validation")
|
||||
|
||||
@classmethod
|
||||
def default_allow_modification_after_validation(cls):
|
||||
return False
|
||||
|
||||
|
||||
class SaleConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'sale.configuration'
|
||||
|
||||
allow_modification_after_validation = fields.Boolean(
|
||||
"Autorise modification after validation")
|
||||
|
||||
@classmethod
|
||||
def default_allow_modification_after_validation(cls):
|
||||
return False
|
||||
|
||||
|
||||
class AccountConfiguration(metaclass=PoolMeta):
|
||||
__name__ = 'account.configuration'
|
||||
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
<field name="type">form</field>
|
||||
<field name="name">template_configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="purchase_configuration_view_form">
|
||||
<field name="model">purchase.configuration</field>
|
||||
<field name="inherit" ref="purchase.configuration_view_form"/>
|
||||
<field name="name">purchase_configuration_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="sale_configuration_view_form">
|
||||
<field name="model">sale.configuration</field>
|
||||
<field name="inherit" ref="sale.configuration_view_form"/>
|
||||
<field name="name">sale_configuration_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.act_window" id="act_purchase_trade_configuration_form">
|
||||
<field name="name">Pricing Configuration</field>
|
||||
|
||||
@@ -21,7 +21,7 @@ PHYSICAL_VALUATION_TYPES = [
|
||||
'market',
|
||||
]
|
||||
DERIVATIVE_VALUATION_TYPES = ['derivative']
|
||||
MTM_VALUATION_TYPES = ['mtm']
|
||||
MTM_VALUATION_TYPES = ['mtm', 'pur. mtm', 'sale mtm']
|
||||
ALL_VALUATION_TYPES = (
|
||||
PHYSICAL_VALUATION_TYPES + DERIVATIVE_VALUATION_TYPES
|
||||
+ MTM_VALUATION_TYPES)
|
||||
@@ -496,6 +496,8 @@ class CTRMMtmPnl(ModelSQL, ModelView):
|
||||
('shipment fee', 'Shipment fee'),
|
||||
('market', 'Market'),
|
||||
('mtm', 'Mtm'),
|
||||
('pur. mtm', 'Pur. Mtm'),
|
||||
('sale mtm', 'Sale Mtm'),
|
||||
('derivative', 'Derivative'),
|
||||
], "Type")
|
||||
reference = fields.Char("Reference")
|
||||
|
||||
@@ -25,7 +25,8 @@ from io import BytesIO
|
||||
from xml.etree import ElementTree
|
||||
from trytond.modules.purchase_trade.purchase import (TRIGGERS)
|
||||
from trytond.modules.purchase_trade.company_defaults import (
|
||||
default_itsa_currency, default_itsa_unit, is_itsa_company)
|
||||
default_itsa_currency, default_itsa_price_calendar,
|
||||
default_itsa_price_fix_type, default_itsa_unit, is_itsa_company)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -594,6 +595,13 @@ class MtmScenario(ModelSQL, ModelView):
|
||||
def default_valuation_date_type():
|
||||
return 'fixed'
|
||||
|
||||
@classmethod
|
||||
def default_calendar(cls):
|
||||
if is_itsa_company():
|
||||
calendar = default_itsa_price_calendar()
|
||||
if calendar:
|
||||
return calendar
|
||||
|
||||
def get_resolved_valuation_date(self, name=None):
|
||||
return self.get_valuation_date()
|
||||
|
||||
@@ -777,12 +785,14 @@ class Mtm(ModelSQL, ModelView):
|
||||
|
||||
currency = fields.Many2One('currency.currency', "Currency")
|
||||
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
return self.price_index.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
return None
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
return self.price_index.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
if self.currency:
|
||||
return self.currency
|
||||
return None
|
||||
|
||||
@fields.depends('price_index','price_matrix')
|
||||
def on_change_with_currency(self):
|
||||
@@ -795,6 +805,18 @@ class Mtm(ModelSQL, ModelView):
|
||||
if currency:
|
||||
return currency
|
||||
|
||||
@classmethod
|
||||
def default_fix_type(cls):
|
||||
if is_itsa_company():
|
||||
fix_type = default_itsa_price_fix_type()
|
||||
if fix_type:
|
||||
return fix_type
|
||||
|
||||
@classmethod
|
||||
def default_ratio(cls):
|
||||
if is_itsa_company():
|
||||
return Decimal('100')
|
||||
|
||||
class PriceMatrix(ModelSQL, ModelView):
|
||||
"Price Matrix"
|
||||
__name__ = 'price.matrix'
|
||||
@@ -934,26 +956,24 @@ class Component(ModelSQL, ModelView):
|
||||
else:
|
||||
return '[' + self.fix_type.name + '] '
|
||||
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
|
||||
@fields.depends(
|
||||
'price_source_type', 'price_index', 'price_matrix',
|
||||
'fixed_currency')
|
||||
def get_cur(self, name=None):
|
||||
if self.price_index:
|
||||
PI = Pool().get('price.price')
|
||||
pi = PI(self.price_index)
|
||||
return pi.price_currency
|
||||
if self.price_matrix:
|
||||
return self.price_matrix.currency
|
||||
return self.fixed_currency
|
||||
|
||||
@fields.depends(
|
||||
'price_source_type', 'price_index', 'price_matrix',
|
||||
'fixed_currency')
|
||||
def on_change_with_currency(self):
|
||||
if self.price_source_type == 'curve' and self.price_index:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'matrix' and self.price_matrix:
|
||||
return self.get_cur()
|
||||
if self.price_source_type == 'fixed':
|
||||
return self.fixed_currency
|
||||
return self.fixed_currency
|
||||
|
||||
@classmethod
|
||||
def default_fixed_currency(cls):
|
||||
@@ -961,6 +981,25 @@ class Component(ModelSQL, ModelView):
|
||||
currency = default_itsa_currency()
|
||||
if currency:
|
||||
return currency
|
||||
|
||||
@classmethod
|
||||
def default_fix_type(cls):
|
||||
if is_itsa_company():
|
||||
fix_type = default_itsa_price_fix_type()
|
||||
if fix_type:
|
||||
return fix_type
|
||||
|
||||
@classmethod
|
||||
def default_ratio(cls):
|
||||
if is_itsa_company():
|
||||
return Decimal('100')
|
||||
|
||||
@classmethod
|
||||
def default_calendar(cls):
|
||||
if is_itsa_company():
|
||||
calendar = default_itsa_price_calendar()
|
||||
if calendar:
|
||||
return calendar
|
||||
|
||||
@classmethod
|
||||
def set_cur(cls, components, name, value):
|
||||
|
||||
@@ -272,6 +272,10 @@ class Purchase(metaclass=PoolMeta):
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._transitions.discard(('confirmed', 'processing'))
|
||||
cls.lines.states['readonly'] = (
|
||||
(Eval('state') != 'draft')
|
||||
& ((Eval('state') != 'quotation')
|
||||
| ~Eval('allow_modification_after_validation')))
|
||||
cls._buttons['process'] = {
|
||||
'invisible': True,
|
||||
'depends': ['state'],
|
||||
@@ -380,11 +384,14 @@ class Purchase(metaclass=PoolMeta):
|
||||
doc_template = fields.Many2One('doc.template',"Template")
|
||||
required_documents = fields.Many2Many(
|
||||
'contract.document.type', 'purchase', 'doc_type', 'Required Documents')
|
||||
analytic_dimensions = fields.One2Many(
|
||||
'analytic.dimension.assignment',
|
||||
'purchase',
|
||||
'Analytic Dimensions'
|
||||
)
|
||||
analytic_dimensions = fields.One2Many(
|
||||
'analytic.dimension.assignment',
|
||||
'purchase',
|
||||
'Analytic Dimensions'
|
||||
)
|
||||
allow_modification_after_validation = fields.Function(
|
||||
fields.Boolean("Autorise modification after validation"),
|
||||
'on_change_with_allow_modification_after_validation')
|
||||
trader = fields.Many2One(
|
||||
'party.party', "Trader",
|
||||
domain=[('categories.name', '=', 'TRADER')])
|
||||
@@ -404,6 +411,13 @@ class Purchase(metaclass=PoolMeta):
|
||||
self.company and self.company.party
|
||||
and self.company.party.name in {'MELYA', 'ITSA'})
|
||||
|
||||
def on_change_with_allow_modification_after_validation(self, name=None):
|
||||
Configuration = Pool().get('purchase.configuration')
|
||||
configurations = Configuration.search([], limit=1)
|
||||
return bool(
|
||||
configurations
|
||||
and configurations[0].allow_modification_after_validation)
|
||||
|
||||
def _get_default_bank_account(self):
|
||||
if not self.party or not self.party.bank_accounts:
|
||||
return None
|
||||
@@ -2087,15 +2101,97 @@ class Line(metaclass=PoolMeta):
|
||||
raise UserError(
|
||||
"Shipment period From date must be before To date.")
|
||||
|
||||
@classmethod
|
||||
def _estimated_bl_relation_field(cls):
|
||||
return 'line'
|
||||
|
||||
@classmethod
|
||||
def _has_estimated_bl_date(cls, estimated_dates):
|
||||
return any(
|
||||
getattr(estimated, 'trigger', None) == 'bldate'
|
||||
for estimated in (estimated_dates or []))
|
||||
|
||||
@classmethod
|
||||
def _values_have_estimated_bl_date(cls, values):
|
||||
for command in values.get('estimated_date') or []:
|
||||
action = command[0]
|
||||
if action == 'create':
|
||||
if any(
|
||||
estimated.get('trigger') == 'bldate'
|
||||
for estimated in command[1]):
|
||||
return True
|
||||
elif action == 'write':
|
||||
actions = iter(command[1:])
|
||||
for _estimated_ids, estimated_values in zip(actions, actions):
|
||||
if estimated_values.get('trigger') == 'bldate':
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _delivery_period_from_values(cls, values):
|
||||
period = values.get('del_period')
|
||||
if period and not hasattr(period, 'beg_date'):
|
||||
period = Pool().get('product.month')(period)
|
||||
return period
|
||||
|
||||
@classmethod
|
||||
def _default_estimated_bl_date(cls, values):
|
||||
period = cls._delivery_period_from_values(values)
|
||||
return getattr(period, 'beg_date', None)
|
||||
|
||||
@classmethod
|
||||
def _set_default_estimated_bl_date_values(cls, values):
|
||||
if cls._values_have_estimated_bl_date(values):
|
||||
return
|
||||
estimated_date = cls._default_estimated_bl_date(values)
|
||||
if not estimated_date:
|
||||
return
|
||||
if values.get('estimated_date') is None:
|
||||
values['estimated_date'] = []
|
||||
values['estimated_date'].append(('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': estimated_date,
|
||||
}]))
|
||||
|
||||
@classmethod
|
||||
def _create_missing_estimated_bl_dates(cls, lines):
|
||||
Estimated = Pool().get('pricing.estimated')
|
||||
values = []
|
||||
relation_field = cls._estimated_bl_relation_field()
|
||||
for line in lines:
|
||||
if cls._has_estimated_bl_date(getattr(line, 'estimated_date', None)):
|
||||
continue
|
||||
del_period = getattr(line, 'del_period', None)
|
||||
estimated_date = getattr(del_period, 'beg_date', None)
|
||||
if not estimated_date:
|
||||
continue
|
||||
values.append({
|
||||
relation_field: line.id,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': estimated_date,
|
||||
})
|
||||
if values:
|
||||
Estimated.create(values)
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
regenerate_valuation = any(
|
||||
cls._should_regenerate_valuation(values) for values in vlist)
|
||||
for values in vlist:
|
||||
cls._check_delivery_period_values([cls()], values)
|
||||
cls._set_default_estimated_bl_date_values(values)
|
||||
cls._set_initial_quantity_values(values)
|
||||
lines = super().create(vlist)
|
||||
if not Transaction().context.get('_purchase_trade_skip_fee_rules'):
|
||||
Pool().get('fee.rule').apply_to_lines(
|
||||
lines, 'purchase_line', auto_only=True)
|
||||
if (regenerate_valuation
|
||||
and not Transaction().context.get(
|
||||
'_purchase_trade_skip_valuation_regeneration')):
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_valuation_regeneration=True):
|
||||
Valuation.regenerate_for_purchase_lines(lines)
|
||||
return lines
|
||||
|
||||
@classmethod
|
||||
@@ -2689,12 +2785,28 @@ class Line(metaclass=PoolMeta):
|
||||
def on_change_linked_unit(self):
|
||||
self._recompute_trade_price_fields()
|
||||
|
||||
@classmethod
|
||||
def _valuation_regeneration_fields(cls):
|
||||
return {
|
||||
'quantity', 'quantity_theorical', 'unit', 'unit_price',
|
||||
'price_type', 'premium', 'linked_price', 'linked_currency',
|
||||
'linked_unit', 'price_pricing', 'price_components', 'derivatives',
|
||||
'fees', 'lots', 'product', 'currency',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _should_regenerate_valuation(cls, values):
|
||||
return bool(cls._valuation_regeneration_fields() & set(values))
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
actions = iter(args)
|
||||
args = []
|
||||
valuation_line_ids = set()
|
||||
for records, values in zip(actions, actions):
|
||||
cls._check_delivery_period_values(records, values)
|
||||
if cls._should_regenerate_valuation(values):
|
||||
valuation_line_ids.update(record.id for record in records)
|
||||
args.extend((records, values))
|
||||
|
||||
# Agents:
|
||||
@@ -2748,9 +2860,18 @@ class Line(metaclass=PoolMeta):
|
||||
|
||||
Pool().get('lot.lot').assert_lines_quantity_consistency(
|
||||
cls._fresh_lines_for_quantity_consistency(lines))
|
||||
cls._create_missing_estimated_bl_dates(lines)
|
||||
if not Transaction().context.get('_purchase_trade_skip_fee_rules'):
|
||||
Pool().get('fee.rule').apply_to_lines(
|
||||
lines, 'purchase_line', auto_only=True)
|
||||
if (valuation_line_ids
|
||||
and not Transaction().context.get(
|
||||
'_purchase_trade_skip_valuation_regeneration')):
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_valuation_regeneration=True):
|
||||
Valuation.regenerate_for_purchase_lines(
|
||||
cls.browse(list(valuation_line_ids)))
|
||||
|
||||
@classmethod
|
||||
def _sync_open_lot_quantity(cls, line, vlot, target_quantity):
|
||||
|
||||
@@ -237,6 +237,10 @@ class Sale(metaclass=PoolMeta):
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
cls._transitions.discard(('confirmed', 'processing'))
|
||||
cls.lines.states['readonly'] = (
|
||||
(Eval('state') != 'draft')
|
||||
& ((Eval('state') != 'quotation')
|
||||
| ~Eval('allow_modification_after_validation')))
|
||||
cls._buttons['process'] = {
|
||||
'invisible': True,
|
||||
'depends': ['state'],
|
||||
@@ -329,6 +333,9 @@ class Sale(metaclass=PoolMeta):
|
||||
'sale',
|
||||
'Analytic Dimensions'
|
||||
)
|
||||
allow_modification_after_validation = fields.Function(
|
||||
fields.Boolean("Autorise modification after validation"),
|
||||
'on_change_with_allow_modification_after_validation')
|
||||
trader = fields.Many2One(
|
||||
'party.party', "Trader",
|
||||
domain=[('categories.name', '=', 'TRADER')])
|
||||
@@ -348,6 +355,13 @@ class Sale(metaclass=PoolMeta):
|
||||
self.company and self.company.party
|
||||
and self.company.party.name in {'MELYA', 'ITSA'})
|
||||
|
||||
def on_change_with_allow_modification_after_validation(self, name=None):
|
||||
Configuration = Pool().get('sale.configuration')
|
||||
configurations = Configuration.search([], limit=1)
|
||||
return bool(
|
||||
configurations
|
||||
and configurations[0].allow_modification_after_validation)
|
||||
|
||||
def _get_default_bank_account(self):
|
||||
if not self.party or not self.party.bank_accounts:
|
||||
return None
|
||||
@@ -1911,15 +1925,97 @@ class SaleLine(metaclass=PoolMeta):
|
||||
raise UserError(
|
||||
"Shipment period From date must be before To date.")
|
||||
|
||||
@classmethod
|
||||
def _estimated_bl_relation_field(cls):
|
||||
return 'sale_line'
|
||||
|
||||
@classmethod
|
||||
def _has_estimated_bl_date(cls, estimated_dates):
|
||||
return any(
|
||||
getattr(estimated, 'trigger', None) == 'bldate'
|
||||
for estimated in (estimated_dates or []))
|
||||
|
||||
@classmethod
|
||||
def _values_have_estimated_bl_date(cls, values):
|
||||
for command in values.get('estimated_date') or []:
|
||||
action = command[0]
|
||||
if action == 'create':
|
||||
if any(
|
||||
estimated.get('trigger') == 'bldate'
|
||||
for estimated in command[1]):
|
||||
return True
|
||||
elif action == 'write':
|
||||
actions = iter(command[1:])
|
||||
for _estimated_ids, estimated_values in zip(actions, actions):
|
||||
if estimated_values.get('trigger') == 'bldate':
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _delivery_period_from_values(cls, values):
|
||||
period = values.get('del_period')
|
||||
if period and not hasattr(period, 'beg_date'):
|
||||
period = Pool().get('product.month')(period)
|
||||
return period
|
||||
|
||||
@classmethod
|
||||
def _default_estimated_bl_date(cls, values):
|
||||
period = cls._delivery_period_from_values(values)
|
||||
return getattr(period, 'beg_date', None)
|
||||
|
||||
@classmethod
|
||||
def _set_default_estimated_bl_date_values(cls, values):
|
||||
if cls._values_have_estimated_bl_date(values):
|
||||
return
|
||||
estimated_date = cls._default_estimated_bl_date(values)
|
||||
if not estimated_date:
|
||||
return
|
||||
if values.get('estimated_date') is None:
|
||||
values['estimated_date'] = []
|
||||
values['estimated_date'].append(('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': estimated_date,
|
||||
}]))
|
||||
|
||||
@classmethod
|
||||
def _create_missing_estimated_bl_dates(cls, lines):
|
||||
Estimated = Pool().get('pricing.estimated')
|
||||
values = []
|
||||
relation_field = cls._estimated_bl_relation_field()
|
||||
for line in lines:
|
||||
if cls._has_estimated_bl_date(getattr(line, 'estimated_date', None)):
|
||||
continue
|
||||
del_period = getattr(line, 'del_period', None)
|
||||
estimated_date = getattr(del_period, 'beg_date', None)
|
||||
if not estimated_date:
|
||||
continue
|
||||
values.append({
|
||||
relation_field: line.id,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': estimated_date,
|
||||
})
|
||||
if values:
|
||||
Estimated.create(values)
|
||||
|
||||
@classmethod
|
||||
def create(cls, vlist):
|
||||
regenerate_valuation = any(
|
||||
cls._should_regenerate_valuation(values) for values in vlist)
|
||||
for values in vlist:
|
||||
cls._check_delivery_period_values([cls()], values)
|
||||
cls._set_default_estimated_bl_date_values(values)
|
||||
cls._set_initial_quantity_values(values)
|
||||
lines = super().create(vlist)
|
||||
if not Transaction().context.get('_purchase_trade_skip_fee_rules'):
|
||||
Pool().get('fee.rule').apply_to_lines(
|
||||
lines, 'sale_line', auto_only=True)
|
||||
if (regenerate_valuation
|
||||
and not Transaction().context.get(
|
||||
'_purchase_trade_skip_valuation_regeneration')):
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_valuation_regeneration=True):
|
||||
Valuation.regenerate_for_sale_lines(lines)
|
||||
return lines
|
||||
|
||||
@classmethod
|
||||
@@ -2631,12 +2727,28 @@ class SaleLine(metaclass=PoolMeta):
|
||||
Pricing.save([p])
|
||||
index += 1
|
||||
|
||||
@classmethod
|
||||
def _valuation_regeneration_fields(cls):
|
||||
return {
|
||||
'quantity', 'quantity_theorical', 'unit', 'unit_price',
|
||||
'price_type', 'premium', 'linked_price', 'linked_currency',
|
||||
'linked_unit', 'price_pricing', 'price_components', 'derivatives',
|
||||
'fees', 'lots', 'product', 'currency',
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _should_regenerate_valuation(cls, values):
|
||||
return bool(cls._valuation_regeneration_fields() & set(values))
|
||||
|
||||
@classmethod
|
||||
def write(cls, *args):
|
||||
actions = iter(args)
|
||||
args = []
|
||||
valuation_line_ids = set()
|
||||
for records, values in zip(actions, actions):
|
||||
cls._check_delivery_period_values(records, values)
|
||||
if cls._should_regenerate_valuation(values):
|
||||
valuation_line_ids.update(record.id for record in records)
|
||||
args.extend((records, values))
|
||||
|
||||
old_values = {}
|
||||
@@ -2684,9 +2796,18 @@ class SaleLine(metaclass=PoolMeta):
|
||||
|
||||
Pool().get('lot.lot').assert_lines_quantity_consistency(
|
||||
cls._fresh_lines_for_quantity_consistency(lines))
|
||||
cls._create_missing_estimated_bl_dates(lines)
|
||||
if not Transaction().context.get('_purchase_trade_skip_fee_rules'):
|
||||
Pool().get('fee.rule').apply_to_lines(
|
||||
lines, 'sale_line', auto_only=True)
|
||||
if (valuation_line_ids
|
||||
and not Transaction().context.get(
|
||||
'_purchase_trade_skip_valuation_regeneration')):
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
with Transaction().set_context(
|
||||
_purchase_trade_skip_valuation_regeneration=True):
|
||||
Valuation.regenerate_for_sale_lines(
|
||||
cls.browse(list(valuation_line_ids)))
|
||||
|
||||
@classmethod
|
||||
def _sync_open_lot_quantity(cls, line, vlot, target_quantity):
|
||||
@@ -3003,6 +3124,7 @@ class ValuationDyn(metaclass=PoolMeta):
|
||||
Sum(val.amount).as_('r_amount'),
|
||||
Sum(val.amount_prev).as_('r_amount_prev'),
|
||||
Sum(val.base_amount).as_('r_base_amount'),
|
||||
Sum(val.pnl).as_('r_pnl'),
|
||||
Max(val.base_currency).as_('r_base_currency'),
|
||||
Sum(val.rate).as_('r_rate'),
|
||||
Avg(val.mtm_price).as_('r_mtm_price'),
|
||||
|
||||
@@ -9,7 +9,7 @@ from unittest.mock import ANY, Mock, call, patch
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from trytond.pool import Pool
|
||||
from trytond.pyson import Eval
|
||||
from trytond.pyson import Eval, PYSONDecoder
|
||||
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
|
||||
from trytond.exceptions import UserError, UserWarning
|
||||
from trytond.transaction import Transaction
|
||||
@@ -902,6 +902,48 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
scenario.get_valuation_date(),
|
||||
datetime.date(2026, 6, 18))
|
||||
|
||||
def test_itsa_mtm_scenario_defaults_calendar(self):
|
||||
'ITSA mtm scenarios default to the sulphuric acid calendar'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_calendar',
|
||||
return_value=17):
|
||||
self.assertEqual(pricing_module.MtmScenario.default_calendar(), 17)
|
||||
|
||||
def test_itsa_mtm_component_defaults(self):
|
||||
'ITSA mtm components default market price, ratio and USD currency'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_fix_type',
|
||||
return_value=21), patch(
|
||||
'trytond.modules.purchase_trade.pricing.default_itsa_currency',
|
||||
return_value=840):
|
||||
self.assertEqual(pricing_module.Mtm.default_fix_type(), 21)
|
||||
self.assertEqual(
|
||||
pricing_module.Mtm.default_ratio(), Decimal('100'))
|
||||
self.assertEqual(pricing_module.Mtm.default_currency(), 840)
|
||||
|
||||
def test_itsa_pricing_component_defaults(self):
|
||||
'ITSA pricing definitions default market price, ratio, USD and calendar'
|
||||
with patch('trytond.modules.purchase_trade.pricing.is_itsa_company',
|
||||
return_value=True), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_fix_type',
|
||||
return_value=21), patch(
|
||||
'trytond.modules.purchase_trade.pricing.'
|
||||
'default_itsa_price_calendar',
|
||||
return_value=17), patch(
|
||||
'trytond.modules.purchase_trade.pricing.default_itsa_currency',
|
||||
return_value=840):
|
||||
self.assertEqual(pricing_module.Component.default_fix_type(), 21)
|
||||
self.assertEqual(
|
||||
pricing_module.Component.default_ratio(), Decimal('100'))
|
||||
self.assertEqual(
|
||||
pricing_module.Component.default_fixed_currency(), 840)
|
||||
self.assertEqual(pricing_module.Component.default_calendar(), 17)
|
||||
|
||||
def test_mtm_scenario_resolves_relative_valuation_dates(self):
|
||||
'mtm scenario can resolve relative valuation dates'
|
||||
scenario = pricing_module.MtmScenario()
|
||||
@@ -1059,7 +1101,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
|
||||
self.assertEqual(len(target), 3)
|
||||
self.assertEqual(target[0], values)
|
||||
self.assertEqual(target[1]['type'], 'mtm')
|
||||
self.assertEqual(target[0]['pnl'], Decimal('-100.00'))
|
||||
self.assertEqual(target[1]['type'], 'pur. mtm')
|
||||
self.assertEqual(target[1]['mtm_curve'], curve_a.id)
|
||||
self.assertEqual(target[1]['price'], None)
|
||||
self.assertEqual(target[1]['amount'], Decimal('0'))
|
||||
@@ -1068,7 +1111,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(target[1]['mtm_price_prev'], Decimal('90.0000'))
|
||||
self.assertEqual(target[1]['amount_prev'], Decimal('-540.00'))
|
||||
self.assertEqual(target[1]['mtm'], Decimal('-600.00'))
|
||||
self.assertEqual(target[2]['type'], 'mtm')
|
||||
self.assertEqual(target[1]['pnl'], Decimal('600.00'))
|
||||
self.assertEqual(target[2]['type'], 'pur. mtm')
|
||||
self.assertEqual(target[2]['mtm_curve'], curve_b.id)
|
||||
self.assertEqual(target[2]['price'], None)
|
||||
self.assertEqual(target[2]['amount'], Decimal('0'))
|
||||
@@ -1077,6 +1121,55 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertEqual(target[2]['mtm_price_prev'], Decimal('45.0000'))
|
||||
self.assertEqual(target[2]['amount_prev'], Decimal('-180.00'))
|
||||
self.assertEqual(target[2]['mtm'], Decimal('-200.00'))
|
||||
self.assertEqual(target[2]['pnl'], Decimal('200.00'))
|
||||
|
||||
@with_transaction()
|
||||
def test_sale_strategy_mtm_lines_use_sale_type(self):
|
||||
'sale strategy MTM lines are labelled as sale MTM'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
line = Mock(unit=Mock())
|
||||
strategy = Mock(
|
||||
scenario=Mock(valuation_date=datetime.date(2026, 6, 5)),
|
||||
currency=None,
|
||||
components=[],
|
||||
get_mtm=Mock(return_value=Decimal('250')))
|
||||
line.mtm = [strategy]
|
||||
values = {
|
||||
'type': 'sale priced',
|
||||
'amount': Decimal('100'),
|
||||
'base_amount': Decimal('100'),
|
||||
'quantity': Decimal('10'),
|
||||
}
|
||||
target = []
|
||||
|
||||
Valuation._append_pnl_values(target, values, line)
|
||||
|
||||
self.assertEqual(target[1]['type'], 'sale mtm')
|
||||
self.assertEqual(target[1]['mtm'], Decimal('250'))
|
||||
self.assertEqual(target[1]['pnl'], Decimal('-250.00'))
|
||||
|
||||
def test_pnl_converts_strategy_mtm_to_base_currency(self):
|
||||
'valuation PnL compares base amount with MTM in base currency'
|
||||
Valuation = valuation_module.Valuation
|
||||
source_currency = Mock(id=1)
|
||||
base_currency = Mock(id=2)
|
||||
strategy = Mock(currency=source_currency, scenario=None)
|
||||
values = {
|
||||
'base_amount': Decimal('100'),
|
||||
'base_currency': base_currency,
|
||||
}
|
||||
Currency = Mock()
|
||||
Currency.compute.return_value = Decimal('90')
|
||||
|
||||
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||
PoolMock.return_value.get.return_value = Currency
|
||||
|
||||
pnl = Valuation._pnl_amount(
|
||||
values, strategy=strategy, mtm_amount=Decimal('120'))
|
||||
|
||||
Currency.compute.assert_called_once_with(
|
||||
source_currency, Decimal('120'), base_currency)
|
||||
self.assertEqual(pnl, Decimal('10.00'))
|
||||
|
||||
def test_purchase_pnl_uses_partial_lotqt_match_quantity(self):
|
||||
'open matched purchase and sale pnl use the matched lot.qt quantity'
|
||||
@@ -1547,6 +1640,28 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertFalse(
|
||||
valuation_module.ValuationProcess._sale_line_is_unmatched(sale_line))
|
||||
|
||||
def test_regenerate_for_purchase_lines_deduplicates_targets(self):
|
||||
'targeted valuation regeneration processes each purchase line once'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
line = Mock(id=10)
|
||||
|
||||
with patch.object(Valuation, 'generate') as generate:
|
||||
Valuation.regenerate_for_purchase_lines([line, line])
|
||||
|
||||
generate.assert_called_once_with(line, valuation_type='all')
|
||||
|
||||
def test_regenerate_for_sale_lines_deduplicates_targets(self):
|
||||
'targeted valuation regeneration processes each sale line once'
|
||||
Valuation = Pool().get('valuation.valuation')
|
||||
sale_line = Mock(id=20)
|
||||
|
||||
with patch.object(
|
||||
Valuation, 'generate_from_sale_line') as generate_from_sale_line:
|
||||
Valuation.regenerate_for_sale_lines([sale_line, sale_line])
|
||||
|
||||
generate_from_sale_line.assert_called_once_with(
|
||||
sale_line, valuation_type='all')
|
||||
|
||||
def test_parse_numbers_supports_inline_and_legacy_separators(self):
|
||||
'parse_numbers keeps supporting inline entry and legacy separators'
|
||||
self.assertEqual(
|
||||
@@ -1567,6 +1682,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
{'derivative'})
|
||||
self.assertIn('pur. priced', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('mtm', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('pur. mtm', Valuation._get_generate_types('goods'))
|
||||
self.assertIn('sale mtm', Valuation._get_generate_types('goods'))
|
||||
|
||||
def test_filter_values_by_types_keeps_matching_entries_only(self):
|
||||
'type filtering keeps only the requested valuation entries'
|
||||
@@ -3596,6 +3713,26 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
self.assertTrue(SaleLine.quantity.readonly)
|
||||
self.assertTrue(PurchaseLine.quantity.readonly)
|
||||
|
||||
def test_allow_modification_after_validation_line_readonly_rule(self):
|
||||
'configuration flag unlocks lines only in quotation state'
|
||||
Sale = Pool().get('sale.sale')
|
||||
Purchase = Pool().get('purchase.purchase')
|
||||
|
||||
for model in (Sale, Purchase):
|
||||
expression = model.lines.states['readonly']
|
||||
self.assertTrue(PYSONDecoder({
|
||||
'state': 'quotation',
|
||||
'allow_modification_after_validation': False,
|
||||
}).decode(expression))
|
||||
self.assertFalse(PYSONDecoder({
|
||||
'state': 'quotation',
|
||||
'allow_modification_after_validation': True,
|
||||
}).decode(expression))
|
||||
self.assertTrue(PYSONDecoder({
|
||||
'state': 'confirmed',
|
||||
'allow_modification_after_validation': True,
|
||||
}).decode(expression))
|
||||
|
||||
def test_purchase_line_initial_quantity_uses_theoretical_quantity(self):
|
||||
'purchase line initializes technical quantity from contractual quantity'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
@@ -4004,6 +4141,84 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
'quantity': Decimal('1'),
|
||||
})
|
||||
|
||||
def test_purchase_and_sale_line_create_default_estimated_bl_date(self):
|
||||
'new lines default missing estimated BL date from delivery period start'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
SaleLine = Pool().get('sale.line')
|
||||
period = Mock(beg_date=datetime.date(2026, 5, 1))
|
||||
purchase_values = {'del_period': period}
|
||||
sale_values = {'del_period': period}
|
||||
|
||||
PurchaseLine._set_default_estimated_bl_date_values(purchase_values)
|
||||
SaleLine._set_default_estimated_bl_date_values(sale_values)
|
||||
|
||||
self.assertEqual(purchase_values['estimated_date'], [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])])
|
||||
self.assertEqual(sale_values['estimated_date'], [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])])
|
||||
|
||||
def test_purchase_and_sale_line_keep_explicit_estimated_bl_date(self):
|
||||
'new lines keep explicit estimated BL date'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
SaleLine = Pool().get('sale.line')
|
||||
period = Mock(beg_date=datetime.date(2026, 5, 1))
|
||||
explicit = [('create', [{
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 9),
|
||||
}])]
|
||||
purchase_values = {'del_period': period, 'estimated_date': list(explicit)}
|
||||
sale_values = {'del_period': period, 'estimated_date': list(explicit)}
|
||||
|
||||
PurchaseLine._set_default_estimated_bl_date_values(purchase_values)
|
||||
SaleLine._set_default_estimated_bl_date_values(sale_values)
|
||||
|
||||
self.assertEqual(purchase_values['estimated_date'], explicit)
|
||||
self.assertEqual(sale_values['estimated_date'], explicit)
|
||||
|
||||
def test_purchase_line_write_creates_missing_estimated_bl_date(self):
|
||||
'purchase line write creates missing estimated BL date from period'
|
||||
PurchaseLine = Pool().get('purchase.line')
|
||||
line = Mock(
|
||||
id=42,
|
||||
del_period=Mock(beg_date=datetime.date(2026, 5, 1)),
|
||||
estimated_date=[],
|
||||
)
|
||||
|
||||
with patch('trytond.modules.purchase_trade.purchase.Pool') as PoolMock:
|
||||
Estimated = Mock()
|
||||
PoolMock.return_value.get.return_value = Estimated
|
||||
PurchaseLine._create_missing_estimated_bl_dates([line])
|
||||
|
||||
Estimated.create.assert_called_once_with([{
|
||||
'line': 42,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])
|
||||
|
||||
def test_sale_line_write_creates_missing_estimated_bl_date(self):
|
||||
'sale line write creates missing estimated BL date from period'
|
||||
SaleLine = Pool().get('sale.line')
|
||||
line = Mock(
|
||||
id=43,
|
||||
del_period=Mock(beg_date=datetime.date(2026, 5, 1)),
|
||||
estimated_date=[],
|
||||
)
|
||||
|
||||
with patch('trytond.modules.purchase_trade.sale.Pool') as PoolMock:
|
||||
Estimated = Mock()
|
||||
PoolMock.return_value.get.return_value = Estimated
|
||||
SaleLine._create_missing_estimated_bl_dates([line])
|
||||
|
||||
Estimated.create.assert_called_once_with([{
|
||||
'sale_line': 43,
|
||||
'trigger': 'bldate',
|
||||
'estimated_date': datetime.date(2026, 5, 1),
|
||||
}])
|
||||
|
||||
def test_sale_and_purchase_parent_write_check_embedded_line_commands(self):
|
||||
'sale and purchase writes validate embedded one2many line commands'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
@@ -33,6 +33,8 @@ VALTYPE = [
|
||||
('shipment fee', 'Shipment fee'),
|
||||
('market', 'Market'),
|
||||
('mtm', 'Mtm'),
|
||||
('pur. mtm', 'Pur. Mtm'),
|
||||
('sale mtm', 'Sale Mtm'),
|
||||
('derivative', 'Derivative'),
|
||||
]
|
||||
|
||||
@@ -67,6 +69,7 @@ class ValuationBase(ModelSQL):
|
||||
shipment_in = fields.Many2One('stock.shipment.in', "Shipment In")
|
||||
fee = fields.Many2One('fee.fee', "Fee")
|
||||
base_amount = fields.Numeric("Base Amount",digits=(16,2))
|
||||
pnl = fields.Numeric("Pnl", digits=(16, 2))
|
||||
base_currency = fields.Many2One('currency.currency', "Base Cur")
|
||||
rate = fields.Numeric("Rate", digits=(16,6))
|
||||
|
||||
@@ -77,7 +80,8 @@ class ValuationBase(ModelSQL):
|
||||
'fees': {'line fee', 'pur. fee', 'sale fee', 'shipment fee'},
|
||||
'goods': {
|
||||
'priced', 'pur. priced', 'pur. efp',
|
||||
'sale priced', 'sale efp', 'market', 'mtm',
|
||||
'sale priced', 'sale efp', 'market',
|
||||
'mtm', 'pur. mtm', 'sale mtm',
|
||||
},
|
||||
'derivatives': {'derivative'},
|
||||
}
|
||||
@@ -370,6 +374,12 @@ class ValuationBase(ModelSQL):
|
||||
seen.add(key)
|
||||
return unique
|
||||
|
||||
@classmethod
|
||||
def _prepare_values_for_create(cls, values):
|
||||
for value in values:
|
||||
cls._set_pnl(value, strategy=value.get('strategy'))
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def _snapshot_identity_domain(cls, value):
|
||||
domain = [
|
||||
@@ -450,6 +460,30 @@ class ValuationBase(ModelSQL):
|
||||
def _sale_line_is_unmatched(cls, sale_line):
|
||||
return not cls._matched_purchase_lines_from_sale_line(sale_line)
|
||||
|
||||
@classmethod
|
||||
def _unique_records(cls, records):
|
||||
unique = []
|
||||
seen = set()
|
||||
for record in records or []:
|
||||
record_id = cls._record_id(record)
|
||||
key = record_id if record_id is not None else id(record)
|
||||
if not record or key in seen:
|
||||
continue
|
||||
unique.append(record)
|
||||
seen.add(key)
|
||||
return unique
|
||||
|
||||
@classmethod
|
||||
def regenerate_for_purchase_lines(cls, lines, valuation_type='all'):
|
||||
for line in cls._unique_records(lines):
|
||||
cls.generate(line, valuation_type=valuation_type)
|
||||
|
||||
@classmethod
|
||||
def regenerate_for_sale_lines(cls, sale_lines, valuation_type='all'):
|
||||
for sale_line in cls._unique_records(sale_lines):
|
||||
cls.generate_from_sale_line(
|
||||
sale_line, valuation_type=valuation_type)
|
||||
|
||||
@classmethod
|
||||
def _delete_existing_sale_line(cls, sale_line, selected_types=None):
|
||||
Date = Pool().get('ir.date')
|
||||
@@ -569,6 +603,68 @@ class ValuationBase(ModelSQL):
|
||||
return -abs(mtm)
|
||||
return abs(mtm)
|
||||
|
||||
@staticmethod
|
||||
def _strategy_mtm_type(values):
|
||||
if values.get('type') in {'pur. priced', 'pur. efp'}:
|
||||
return 'pur. mtm'
|
||||
if values.get('type') in {'sale priced', 'sale efp'}:
|
||||
return 'sale mtm'
|
||||
return 'mtm'
|
||||
|
||||
@staticmethod
|
||||
def _coerce_decimal(value):
|
||||
if value in (None, ''):
|
||||
return Decimal(0)
|
||||
return Decimal(str(value))
|
||||
|
||||
@classmethod
|
||||
def _mtm_base_amount(cls, mtm_amount, strategy, values):
|
||||
if mtm_amount in (None, ''):
|
||||
return Decimal(0)
|
||||
mtm_amount = Decimal(str(mtm_amount))
|
||||
source_currency = getattr(strategy, 'currency', None)
|
||||
base_currency = values.get('base_currency')
|
||||
if not source_currency or not base_currency:
|
||||
return mtm_amount
|
||||
|
||||
source_currency_id = cls._record_id(source_currency)
|
||||
base_currency_id = cls._record_id(base_currency)
|
||||
if source_currency_id == base_currency_id:
|
||||
return mtm_amount
|
||||
|
||||
Currency = Pool().get('currency.currency')
|
||||
if isinstance(source_currency, int):
|
||||
source_currency = Currency(source_currency)
|
||||
if isinstance(base_currency, int):
|
||||
base_currency = Currency(base_currency)
|
||||
|
||||
scenario = getattr(strategy, 'scenario', None)
|
||||
conversion_date = (
|
||||
cls._strategy_valuation_date(strategy)
|
||||
if scenario else values.get('date'))
|
||||
if conversion_date:
|
||||
with Transaction().set_context(date=conversion_date):
|
||||
return Currency.compute(
|
||||
source_currency, mtm_amount, base_currency)
|
||||
return Currency.compute(source_currency, mtm_amount, base_currency)
|
||||
|
||||
@classmethod
|
||||
def _pnl_amount(cls, values, strategy=None, mtm_amount=None):
|
||||
base_amount = cls._coerce_decimal(values.get('base_amount'))
|
||||
if mtm_amount is None:
|
||||
mtm_amount = values.get('mtm')
|
||||
mtm_base_amount = (
|
||||
cls._mtm_base_amount(mtm_amount, strategy, values)
|
||||
if strategy else cls._coerce_decimal(mtm_amount))
|
||||
return round(base_amount - mtm_base_amount, 2)
|
||||
|
||||
@classmethod
|
||||
def _set_pnl(cls, values, strategy=None, mtm_amount=None):
|
||||
if values is not None:
|
||||
values['pnl'] = cls._pnl_amount(
|
||||
values, strategy=strategy, mtm_amount=mtm_amount)
|
||||
return values
|
||||
|
||||
@staticmethod
|
||||
def _component_weight(component):
|
||||
ratio = Decimal(component.ratio or 0)
|
||||
@@ -623,7 +719,7 @@ class ValuationBase(ModelSQL):
|
||||
mtm_curve=None, mtm_price_prev=None, mtm_amount_prev=None):
|
||||
line_values = dict(values)
|
||||
line_values.update({
|
||||
'type': 'mtm',
|
||||
'type': cls._strategy_mtm_type(values),
|
||||
'reference': 'MTM/%s' % (values.get('reference') or ''),
|
||||
'price': None,
|
||||
'amount': Decimal(0),
|
||||
@@ -635,6 +731,7 @@ class ValuationBase(ModelSQL):
|
||||
'mtm': mtm_amount,
|
||||
'strategy': strategy,
|
||||
})
|
||||
cls._set_pnl(line_values, strategy=strategy, mtm_amount=mtm_amount)
|
||||
return line_values
|
||||
|
||||
@classmethod
|
||||
@@ -842,11 +939,13 @@ class ValuationBase(ModelSQL):
|
||||
def _append_pnl_values(cls, price_lines, values, mtm_source):
|
||||
if (values and getattr(mtm_source, 'mtm', None)
|
||||
and cls._supports_strategy_mtm(values)):
|
||||
cls._set_pnl(values)
|
||||
price_lines.append(values)
|
||||
for strat in mtm_source.mtm:
|
||||
cls._append_strategy_mtm_lines(
|
||||
price_lines, values, strat, mtm_source)
|
||||
elif values:
|
||||
cls._set_pnl(values)
|
||||
price_lines.append(values)
|
||||
|
||||
@classmethod
|
||||
@@ -1467,6 +1566,7 @@ class ValuationBase(ModelSQL):
|
||||
values.extend(cls.create_pnl_der_from_line(line))
|
||||
values = cls._filter_values_by_types(values, selected_types)
|
||||
values = cls._dedupe_values(values)
|
||||
values = cls._prepare_values_for_create(values)
|
||||
cls._delete_existing_snapshot_values(values)
|
||||
|
||||
if values:
|
||||
@@ -1492,6 +1592,7 @@ class ValuationBase(ModelSQL):
|
||||
values.extend(cls.create_pnl_der_from_sale_line(sale_line))
|
||||
values = cls._filter_values_by_types(values, selected_types)
|
||||
values = cls._dedupe_values(values)
|
||||
values = cls._prepare_values_for_create(values)
|
||||
cls._delete_existing_snapshot_values(values)
|
||||
|
||||
if values:
|
||||
@@ -1589,6 +1690,7 @@ class ValuationDyn(ModelSQL,ModelView):
|
||||
r_amount = fields.Numeric("Amount",digits='r_unit')
|
||||
r_amount_prev = fields.Numeric("Amount -1", digits='r_unit')
|
||||
r_base_amount = fields.Numeric("Base Amount",digits='r_unit')
|
||||
r_pnl = fields.Numeric("Pnl", digits='r_unit')
|
||||
r_base_currency = fields.Many2One('currency.currency', "Base Cur")
|
||||
r_rate = fields.Numeric("Rate",digits=(16,6))
|
||||
r_mtm_price = fields.Numeric("Mtm Price",digits='r_unit')
|
||||
@@ -1628,6 +1730,7 @@ class ValuationDyn(ModelSQL,ModelView):
|
||||
Sum(val.amount).as_('r_amount'),
|
||||
Sum(val.amount_prev).as_('r_amount_prev'),
|
||||
Sum(val.base_amount).as_('r_base_amount'),
|
||||
Sum(val.pnl).as_('r_pnl'),
|
||||
Max(val.base_currency).as_('r_base_currency'),
|
||||
Sum(val.rate).as_('r_rate'),
|
||||
Avg(val.mtm_price).as_('r_mtm_price'),
|
||||
@@ -1686,6 +1789,7 @@ class ValuationReport(ValuationBase, ModelView):
|
||||
val.amount.as_('amount'),
|
||||
val.amount_prev.as_('amount_prev'),
|
||||
val.base_amount.as_('base_amount'),
|
||||
val.pnl.as_('pnl'),
|
||||
val.base_currency.as_('base_currency'),
|
||||
val.rate.as_('rate'),
|
||||
val.mtm_price.as_('mtm_price'),
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/label[@name='purchase_process_after']" position="before">
|
||||
<label name="allow_modification_after_validation"/>
|
||||
<field name="allow_modification_after_validation"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -3,6 +3,9 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/group[@id='hd'][2]" position="replace"/>
|
||||
<xpath expr="/form/field[@name='party_lang']" position="before">
|
||||
<field name="allow_modification_after_validation" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/group[@id='hd'][1]" position="replace">
|
||||
<group id="purchase_header_summary" colspan="6" col="10"
|
||||
panel="summary" xalign="0" yalign="0"
|
||||
|
||||
8
modules/purchase_trade/view/sale_configuration_form.xml
Normal file
8
modules/purchase_trade/view/sale_configuration_form.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<data>
|
||||
<xpath expr="/form/label[@name='sale_process_after']" position="before">
|
||||
<label name="allow_modification_after_validation"/>
|
||||
<field name="allow_modification_after_validation"/>
|
||||
<newline/>
|
||||
</xpath>
|
||||
</data>
|
||||
@@ -3,6 +3,9 @@
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<data>
|
||||
<xpath expr="/form/group[@id='hd'][2]" position="replace"/>
|
||||
<xpath expr="/form/field[@name='party_lang']" position="before">
|
||||
<field name="allow_modification_after_validation" invisible="1"/>
|
||||
</xpath>
|
||||
<xpath expr="/form/group[@id='hd'][1]" position="replace">
|
||||
<group id="sale_header_summary" colspan="6" col="10"
|
||||
panel="summary" xalign="0" yalign="0"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="fee" optional="1"/>
|
||||
<field name="type"
|
||||
widget="badge"
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,pur. mtm:#9f1239,Pur. Mtm:#9f1239,sale mtm:#9f1239,Sale Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
<field name="reference"/>
|
||||
<field name="counterparty"/>
|
||||
<field name="state"/>
|
||||
@@ -19,6 +19,7 @@
|
||||
<field name="currency"/>
|
||||
<field name="amount_prev" optional="1"/>
|
||||
<field name="base_amount" sum="1"/>
|
||||
<field name="pnl" sum="1"/>
|
||||
<field name="base_currency"/>
|
||||
<field name="rate"/>
|
||||
<field name="strategy"/>
|
||||
|
||||
@@ -7,7 +7,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="fee" optional="1"/>
|
||||
<field name="type"
|
||||
widget="badge"
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
badge_colors="priced:#2563eb,Price:#2563eb,pur. priced:#1d4ed8,Pur. price:#1d4ed8,pur. efp:#38bdf8,Pur. efp:#38bdf8,sale priced:#16a34a,Sale price:#16a34a,sale efp:#86efac,Sale efp:#86efac,market:#64748b,Market:#64748b,line fee:#d97706,Line fee:#d97706,pur. fee:#f59e0b,Pur. fee:#f59e0b,sale fee:#fb923c,Sale fee:#fb923c,shipment fee:#0f766e,Shipment fee:#0f766e,mtm:#9f1239,Mtm:#9f1239,pur. mtm:#9f1239,Pur. Mtm:#9f1239,sale mtm:#9f1239,Sale Mtm:#9f1239,derivative:#8b5cf6,Derivative:#8b5cf6,*:#94a3b8"/>
|
||||
<field name="reference"/>
|
||||
<field name="counterparty"/>
|
||||
<field name="state"/>
|
||||
@@ -20,6 +20,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="currency"/>
|
||||
<field name="amount_prev" optional="1"/>
|
||||
<field name="base_amount" sum="1"/>
|
||||
<field name="pnl" sum="1"/>
|
||||
<field name="base_currency"/>
|
||||
<field name="rate"/>
|
||||
<field name="strategy"/>
|
||||
|
||||
@@ -15,6 +15,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
sum_previous="r_amount_prev"
|
||||
sum_variation="1"/>
|
||||
<field name="r_amount_prev" optional="1"/>
|
||||
<field name="r_pnl" sum="1"/>
|
||||
<field name="r_strategy"/>
|
||||
<field name="r_mtm_curve"/>
|
||||
<field name="r_mtm_price"
|
||||
|
||||
Reference in New Issue
Block a user