Add Fixed type component
This commit is contained in:
@@ -530,6 +530,14 @@ class MtmStrategy(ModelSQL, ModelView):
|
|||||||
elif comp.price_source_type == 'matrix' and comp.price_matrix:
|
elif comp.price_source_type == 'matrix' and comp.price_matrix:
|
||||||
value = self._get_matrix_price(comp, line, dt)
|
value = self._get_matrix_price(comp, line, dt)
|
||||||
|
|
||||||
|
elif comp.price_source_type == 'fixed':
|
||||||
|
value = Decimal(comp.get_price(
|
||||||
|
dt,
|
||||||
|
line.unit,
|
||||||
|
self.currency,
|
||||||
|
last=scenario.use_last_price
|
||||||
|
))
|
||||||
|
|
||||||
if comp.ratio:
|
if comp.ratio:
|
||||||
value *= Decimal(comp.ratio) / Decimal(100)
|
value *= Decimal(comp.ratio) / Decimal(100)
|
||||||
|
|
||||||
@@ -694,14 +702,29 @@ class Component(ModelSQL, ModelView):
|
|||||||
price_source_type = fields.Selection([
|
price_source_type = fields.Selection([
|
||||||
('curve', 'Curve'),
|
('curve', 'Curve'),
|
||||||
('matrix', 'Matrix'),
|
('matrix', 'Matrix'),
|
||||||
# ('manual', 'Manual'),
|
('fixed', 'Fixed'),
|
||||||
], "Price Source", required=True)
|
], "Price Source", required=True)
|
||||||
|
|
||||||
fix_type = fields.Many2One('price.fixtype',"Fixation type")
|
fix_type = fields.Many2One('price.fixtype',"Fixation type")
|
||||||
ratio = fields.Numeric("%",digits=(16,7))
|
ratio = fields.Numeric("%",digits=(16,7))
|
||||||
price_index = fields.Many2One('price.price',"Curve")
|
price_index = fields.Many2One('price.price',"Curve")
|
||||||
price_matrix = fields.Many2One('price.matrix', "Price Matrix")
|
price_matrix = fields.Many2One('price.matrix', "Price Matrix")
|
||||||
currency = fields.Function(fields.Many2One('currency.currency',"Curr."),'get_cur')
|
fixed_price = fields.Numeric(
|
||||||
|
"Fixed Price", digits=(16, 6),
|
||||||
|
states={
|
||||||
|
'invisible': Eval('price_source_type') != 'fixed',
|
||||||
|
'required': Eval('price_source_type') == 'fixed',
|
||||||
|
},
|
||||||
|
depends=['price_source_type'])
|
||||||
|
fixed_currency = fields.Many2One('currency.currency', "Fixed Curr.")
|
||||||
|
currency = fields.Function(
|
||||||
|
fields.Many2One(
|
||||||
|
'currency.currency', "Curr.",
|
||||||
|
states={
|
||||||
|
'required': Eval('price_source_type') == 'fixed',
|
||||||
|
},
|
||||||
|
depends=['price_source_type']),
|
||||||
|
'get_cur', setter='set_cur')
|
||||||
auto = fields.Boolean("Auto")
|
auto = fields.Boolean("Auto")
|
||||||
fallback = fields.Boolean("Fallback")
|
fallback = fields.Boolean("Fallback")
|
||||||
calendar = fields.Many2One('price.calendar',"Calendar")
|
calendar = fields.Many2One('price.calendar',"Calendar")
|
||||||
@@ -714,16 +737,35 @@ class Component(ModelSQL, ModelView):
|
|||||||
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
return '[' + self.fix_type.name + '] ' + self.price_index.price_index
|
||||||
if self.price_matrix:
|
if self.price_matrix:
|
||||||
return '[' + self.fix_type.name + '] ' + self.price_matrix.name
|
return '[' + self.fix_type.name + '] ' + self.price_matrix.name
|
||||||
|
if self.price_source_type == 'fixed':
|
||||||
|
return '[' + self.fix_type.name + '] Fixed'
|
||||||
else:
|
else:
|
||||||
return '[' + self.fix_type.name + '] '
|
return '[' + self.fix_type.name + '] '
|
||||||
|
|
||||||
def get_cur(self,name):
|
def get_cur(self, name=None):
|
||||||
if self.price_index:
|
if self.price_index:
|
||||||
PI = Pool().get('price.price')
|
PI = Pool().get('price.price')
|
||||||
pi = PI(self.price_index)
|
pi = PI(self.price_index)
|
||||||
return pi.price_currency
|
return pi.price_currency
|
||||||
if self.price_matrix:
|
if self.price_matrix:
|
||||||
return self.price_matrix.currency
|
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 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
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_cur(cls, components, name, value):
|
||||||
|
cls.write(components, {'fixed_currency': value})
|
||||||
|
|
||||||
def get_calendar(self):
|
def get_calendar(self):
|
||||||
if self.calendar:
|
if self.calendar:
|
||||||
@@ -803,6 +845,17 @@ class Component(ModelSQL, ModelView):
|
|||||||
price_qt *= Decimal(str(rate))
|
price_qt *= Decimal(str(rate))
|
||||||
return round(price_qt, 4)
|
return round(price_qt, 4)
|
||||||
|
|
||||||
|
def _convert_fixed_price(self, price, currency):
|
||||||
|
price_qt = Decimal(str(price or 0))
|
||||||
|
price_currency = getattr(self, 'fixed_currency', None)
|
||||||
|
if price_currency and currency and currency != price_currency:
|
||||||
|
Currency = Pool().get('currency.currency')
|
||||||
|
rates = Currency._get_rate([price_currency])
|
||||||
|
rate = rates.get(price_currency.id)
|
||||||
|
if rate:
|
||||||
|
price_qt *= Decimal(str(rate))
|
||||||
|
return round(price_qt, 4)
|
||||||
|
|
||||||
def get_price(self, price_date, unit, currency, last=False):
|
def get_price(self, price_date, unit, currency, last=False):
|
||||||
if self.price_source_type == 'curve' and self.price_index:
|
if self.price_source_type == 'curve' and self.price_index:
|
||||||
PI = Pool().get('price.price')
|
PI = Pool().get('price.price')
|
||||||
@@ -815,6 +868,8 @@ class Component(ModelSQL, ModelView):
|
|||||||
if line:
|
if line:
|
||||||
return self._convert_matrix_price(
|
return self._convert_matrix_price(
|
||||||
line.price_value, unit, currency)
|
line.price_value, unit, currency)
|
||||||
|
if self.price_source_type == 'fixed':
|
||||||
|
return self._convert_fixed_price(self.fixed_price, currency)
|
||||||
return Decimal(0)
|
return Decimal(0)
|
||||||
|
|
||||||
def get_nbdays(self, name):
|
def get_nbdays(self, name):
|
||||||
|
|||||||
@@ -80,6 +80,29 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
strategy.get_mtm(line, Decimal('10')),
|
strategy.get_mtm(line, Decimal('10')),
|
||||||
Decimal('250.00'))
|
Decimal('250.00'))
|
||||||
|
|
||||||
|
@with_transaction()
|
||||||
|
def test_get_mtm_uses_fixed_pricing_component(self):
|
||||||
|
'get_mtm values fixed components like a constant price curve'
|
||||||
|
Strategy = Pool().get('mtm.strategy')
|
||||||
|
strategy = Strategy()
|
||||||
|
strategy.scenario = Mock(
|
||||||
|
valuation_date='2026-03-29',
|
||||||
|
use_last_price=True,
|
||||||
|
)
|
||||||
|
strategy.currency = Mock()
|
||||||
|
strategy.components = [Mock(
|
||||||
|
price_source_type='fixed',
|
||||||
|
price_index=None,
|
||||||
|
price_matrix=None,
|
||||||
|
get_price=Mock(return_value=Decimal('75')),
|
||||||
|
ratio=Decimal('100'),
|
||||||
|
)]
|
||||||
|
line = Mock(unit=Mock())
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
strategy.get_mtm(line, Decimal('3')),
|
||||||
|
Decimal('225.00'))
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_add_physical_lot_defaults_hidden_premium_and_chunk_key(self):
|
def test_add_physical_lot_defaults_hidden_premium_and_chunk_key(self):
|
||||||
'add physical lot works when hidden tree fields are not loaded'
|
'add physical lot works when hidden tree fields are not loaded'
|
||||||
@@ -182,6 +205,28 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
valuation_module.Valuation._get_strategy_mtm_price(strategy, line),
|
valuation_module.Valuation._get_strategy_mtm_price(strategy, line),
|
||||||
Decimal('40.0000'))
|
Decimal('40.0000'))
|
||||||
|
|
||||||
|
def test_get_strategy_mtm_price_uses_fixed_pricing_component(self):
|
||||||
|
'strategy mtm price uses fixed components like constant price curves'
|
||||||
|
strategy = Mock(
|
||||||
|
scenario=Mock(
|
||||||
|
valuation_date='2026-03-29',
|
||||||
|
use_last_price=True,
|
||||||
|
),
|
||||||
|
currency=Mock(),
|
||||||
|
)
|
||||||
|
strategy.components = [Mock(
|
||||||
|
price_source_type='fixed',
|
||||||
|
price_index=None,
|
||||||
|
price_matrix=None,
|
||||||
|
get_price=Mock(return_value=Decimal('75')),
|
||||||
|
ratio=Decimal('50'),
|
||||||
|
)]
|
||||||
|
line = Mock(unit=Mock())
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
valuation_module.Valuation._get_strategy_mtm_price(strategy, line),
|
||||||
|
Decimal('37.5000'))
|
||||||
|
|
||||||
def test_sale_line_is_unmatched_checks_lot_links(self):
|
def test_sale_line_is_unmatched_checks_lot_links(self):
|
||||||
'sale line unmatched helper ignores empty matches and detects linked purchases'
|
'sale line unmatched helper ignores empty matches and detects linked purchases'
|
||||||
sale_line = Mock()
|
sale_line = Mock()
|
||||||
@@ -1941,6 +1986,39 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
|
|
||||||
self.assertEqual(component.get_calendar(), calendar)
|
self.assertEqual(component.get_calendar(), calendar)
|
||||||
|
|
||||||
|
def test_pricing_component_fixed_returns_flat_price(self):
|
||||||
|
'fixed pricing returns the component flat price'
|
||||||
|
Component = Pool().get('pricing.component')
|
||||||
|
component = Component()
|
||||||
|
component.price_source_type = 'fixed'
|
||||||
|
component.fixed_price = Decimal('125.25')
|
||||||
|
currency = Mock(id=1)
|
||||||
|
component.fixed_currency = currency
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
component.get_price(
|
||||||
|
datetime.date(2026, 4, 1), Mock(), currency, False),
|
||||||
|
Decimal('125.2500'))
|
||||||
|
|
||||||
|
def test_pricing_component_fixed_converts_currency(self):
|
||||||
|
'fixed pricing converts from component currency to requested currency'
|
||||||
|
Component = Pool().get('pricing.component')
|
||||||
|
component = Component()
|
||||||
|
component.price_source_type = 'fixed'
|
||||||
|
component.fixed_price = Decimal('100')
|
||||||
|
component.fixed_currency = Mock(id=1)
|
||||||
|
target_currency = Mock(id=2)
|
||||||
|
currency_model = Mock(
|
||||||
|
_get_rate=Mock(return_value={
|
||||||
|
component.fixed_currency.id: Decimal('1.2')}))
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.pricing.Pool') as PricingPool:
|
||||||
|
PricingPool.return_value.get.return_value = currency_model
|
||||||
|
price = component.get_price(
|
||||||
|
datetime.date(2026, 4, 1), Mock(), target_currency, False)
|
||||||
|
|
||||||
|
self.assertEqual(price, Decimal('120.0000'))
|
||||||
|
|
||||||
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
|
def test_sale_and_purchase_trader_operator_domains_use_explicit_categories(self):
|
||||||
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
|
'sale and purchase trader/operator fields are filtered by TRADER/OPERATOR categories'
|
||||||
Sale = Pool().get('sale.sale')
|
Sale = Pool().get('sale.sale')
|
||||||
|
|||||||
@@ -356,6 +356,14 @@ class ValuationBase(ModelSQL):
|
|||||||
value = Decimal(strategy._get_matrix_price(
|
value = Decimal(strategy._get_matrix_price(
|
||||||
comp, line, scenario.valuation_date))
|
comp, line, scenario.valuation_date))
|
||||||
|
|
||||||
|
elif comp.price_source_type == 'fixed':
|
||||||
|
value = Decimal(comp.get_price(
|
||||||
|
scenario.valuation_date,
|
||||||
|
line.unit,
|
||||||
|
strategy.currency,
|
||||||
|
last=scenario.use_last_price
|
||||||
|
))
|
||||||
|
|
||||||
if comp.ratio:
|
if comp.ratio:
|
||||||
value *= Decimal(comp.ratio) / Decimal(100)
|
value *= Decimal(comp.ratio) / Decimal(100)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<form col="6">
|
<form col="6">
|
||||||
|
<label name="price_source_type"/>
|
||||||
|
<field name="price_source_type"/>
|
||||||
|
<newline/>
|
||||||
<label name="fix_type"/>
|
<label name="fix_type"/>
|
||||||
<field name="fix_type"/>
|
<field name="fix_type"/>
|
||||||
<label name="ratio"/>
|
<label name="ratio"/>
|
||||||
@@ -8,6 +11,8 @@
|
|||||||
<field name="price_index"/>
|
<field name="price_index"/>
|
||||||
<label name="currency"/>
|
<label name="currency"/>
|
||||||
<field name="currency"/>
|
<field name="currency"/>
|
||||||
|
<label name="fixed_price"/>
|
||||||
|
<field name="fixed_price"/>
|
||||||
<newline/>
|
<newline/>
|
||||||
<label name="auto"/>
|
<label name="auto"/>
|
||||||
<field name="auto"/>
|
<field name="auto"/>
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<form col="6">
|
<form col="6">
|
||||||
|
<label name="price_source_type"/>
|
||||||
|
<field name="price_source_type"/>
|
||||||
|
<newline/>
|
||||||
<label name="fix_type"/>
|
<label name="fix_type"/>
|
||||||
<field name="fix_type"/>
|
<field name="fix_type"/>
|
||||||
<label name="ratio"/>
|
<label name="ratio"/>
|
||||||
@@ -8,6 +11,8 @@
|
|||||||
<field name="price_index"/>
|
<field name="price_index"/>
|
||||||
<label name="currency"/>
|
<label name="currency"/>
|
||||||
<field name="currency"/>
|
<field name="currency"/>
|
||||||
|
<label name="fixed_price"/>
|
||||||
|
<field name="fixed_price"/>
|
||||||
<newline/>
|
<newline/>
|
||||||
<label name="auto"/>
|
<label name="auto"/>
|
||||||
<field name="auto"/>
|
<field name="auto"/>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<field name="price_index"/>
|
<field name="price_index"/>
|
||||||
<field name="price_matrix"/>
|
<field name="price_matrix"/>
|
||||||
<field name="currency" width="60"/>
|
<field name="currency" width="60"/>
|
||||||
|
<field name="fixed_price" width="80"/>
|
||||||
<field name="auto" width="60"/>
|
<field name="auto" width="60"/>
|
||||||
<field name="fallback" width="80"/>
|
<field name="fallback" width="80"/>
|
||||||
<field name="calendar"/>
|
<field name="calendar"/>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<field name="price_index"/>
|
<field name="price_index"/>
|
||||||
<field name="price_matrix"/>
|
<field name="price_matrix"/>
|
||||||
<field name="currency"/>
|
<field name="currency"/>
|
||||||
|
<field name="fixed_price"/>
|
||||||
<field name="auto"/>
|
<field name="auto"/>
|
||||||
<field name="fallback"/>
|
<field name="fallback"/>
|
||||||
<field name="calendar"/>
|
<field name="calendar"/>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<field name="price_index"/>
|
<field name="price_index"/>
|
||||||
<field name="price_matrix"/>
|
<field name="price_matrix"/>
|
||||||
<field name="currency"/>
|
<field name="currency"/>
|
||||||
|
<field name="fixed_price"/>
|
||||||
<field name="auto"/>
|
<field name="auto"/>
|
||||||
<field name="fallback"/>
|
<field name="fallback"/>
|
||||||
<field name="calendar"/>
|
<field name="calendar"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user