Pricing low mid high

This commit is contained in:
2026-08-10 09:09:23 +02:00
parent 0ac2e8514a
commit c50965a03b
9 changed files with 101 additions and 25 deletions

View File

@@ -89,9 +89,15 @@ class Price(
amount = Decimal(self.get_price_per_qt(price,unit,currency)) * Decimal(Uom.compute_qty(self.price_unit, float(self.price_ct_size * nb_ct), unit))
return round(amount,4)
def get_price(self,dt,unit,currency,last=False, relative_last=False):
def get_price(
self, dt, unit, currency, last=False, relative_last=False,
price_field='price_value'):
price = float(0)
PV = Pool().get('price.price_value')
price_field = price_field or 'price_value'
if price_field not in {
'price_value', 'low_price', 'mid_price', 'high_price'}:
price_field = 'price_value'
if self.price_values:
dt = dt.strftime("%Y-%m-%d")
pv = PV.search([('price','=',self.id),('price_date','=',dt)])
@@ -103,7 +109,8 @@ class Price(
('price_date','<=',dt),
], order=[('price_date', 'DESC')])
if pv:
price = self.get_price_per_qt(pv[0].price_value,unit,currency)
price = self.get_price_per_qt(
getattr(pv[0], price_field, None), unit, currency)
return round(price,4)
class FixType(ModelSQL,ModelView):

View File

@@ -886,17 +886,15 @@ class MtmStrategy(ModelSQL, ModelView):
for comp in self.components:
value = Decimal(0)
if comp.price_source_type == 'curve' and comp.price_index:
value = Decimal(
comp.price_index.get_price(
dt,
line.unit,
self.currency,
relative_last=scenario.use_last_price
)
)
elif comp.price_source_type == 'matrix' and comp.price_matrix:
if comp.price_source_type == 'curve' and comp.price_index:
value = Decimal(comp.get_price(
dt,
line.unit,
self.currency,
relative_last=scenario.use_last_price
))
elif comp.price_source_type == 'matrix' and comp.price_matrix:
value = self._get_matrix_price(comp, line, dt)
elif comp.price_source_type == 'fixed':
@@ -1104,11 +1102,22 @@ class Component(ModelSQL, ModelView):
required=False, ondelete='CASCADE'
)
price_source_type = fields.Selection([
('curve', 'Curve'),
('matrix', 'Matrix'),
('fixed', 'Fixed'),
], "Price Source", required=True)
price_source_type = fields.Selection([
('curve', 'Curve'),
('matrix', 'Matrix'),
('fixed', 'Fixed'),
], "Price Source", required=True)
curve_price_field = fields.Selection([
('price_value', 'Price'),
('low_price', 'Low'),
('mid_price', 'Mid'),
('high_price', 'High'),
], "Curve Value",
states={
'readonly': Eval('price_source_type') != 'curve',
'required': Eval('price_source_type') == 'curve',
},
depends=['price_source_type'])
fix_type = fields.Many2One('price.fixtype',"Fixation type")
ratio = fields.Numeric("%",digits=(16,7))
@@ -1197,6 +1206,10 @@ class Component(ModelSQL, ModelView):
if is_itsa_company():
return Decimal('100')
@classmethod
def default_curve_price_field(cls):
return 'price_value'
@classmethod
def default_calendar(cls):
if is_itsa_company():
@@ -1393,7 +1406,8 @@ class Component(ModelSQL, ModelView):
PI = Pool().get('price.price')
pi = PI(self.price_index)
return pi.get_price(
price_date, unit, currency, last, relative_last)
price_date, unit, currency, last, relative_last,
self.curve_price_field or 'price_value')
if self.price_source_type == 'matrix' and self.price_matrix:
if not self._matrix_valid_on(price_date):
return Decimal(0)

View File

@@ -6060,7 +6060,27 @@ description</t></is></c>
self.assertEqual(price, Decimal('101'))
price_index.get_price.assert_called_once_with(
datetime.date(2026, 4, 1), ANY, ANY, False, True)
datetime.date(2026, 4, 1), ANY, ANY, False, True, 'price_value')
def test_pricing_component_curve_passes_selected_price_field(self):
'curve pricing can select high, mid or low values'
Component = Pool().get('pricing.component')
price_index = Mock()
component = Component()
component.price_source_type = 'curve'
component.price_index = price_index
component.curve_price_field = 'high_price'
price_model = Mock(return_value=price_index)
price_index.get_price = Mock(return_value=Decimal('103'))
with patch('trytond.modules.purchase_trade.pricing.Pool') as PricingPool:
PricingPool.return_value.get.return_value = price_model
price = component.get_price(
datetime.date(2026, 4, 1), Mock(), Mock(), False, True)
self.assertEqual(price, Decimal('103'))
price_index.get_price.assert_called_once_with(
datetime.date(2026, 4, 1), ANY, ANY, False, True, 'high_price')
def test_price_get_price_relative_last_uses_latest_before_date(self):
'relative last picks latest available price before target date'
@@ -6085,6 +6105,28 @@ description</t></is></c>
('price_date', '<=', '2026-04-10'),
], order=[('price_date', 'DESC')])
def test_price_get_price_uses_selected_price_field(self):
'market price lookup can read low, mid or high values'
Price = Pool().get('price.price')
price = Price()
price.id = 42
price.price_values = [Mock()]
price.get_price_per_qt = Mock(return_value=Decimal('91'))
price_value = Mock(
price_value=Decimal('88'),
high_price=Decimal('91'))
price_value_model = Mock(search=Mock(return_value=[price_value]))
with patch('trytond.modules.price.price.Pool') as PricePool:
PricePool.return_value.get.return_value = price_value_model
result = price.get_price(
datetime.date(2026, 4, 10), Mock(), Mock(),
price_field='high_price')
self.assertEqual(result, Decimal('91'))
price.get_price_per_qt.assert_called_once_with(
Decimal('91'), ANY, ANY)
def test_price_get_price_per_qt_converts_linked_currency_to_main_currency(self):
'price curve values in linked currency are normalized to main currency'
Price = Pool().get('price.price')

View File

@@ -637,7 +637,7 @@ class ValuationBase(ModelSQL):
value = Decimal(0)
if comp.price_source_type == 'curve' and comp.price_index:
value = Decimal(comp.price_index.get_price(
value = Decimal(comp.get_price(
valuation_date,
line.unit,
strategy.currency,
@@ -755,7 +755,9 @@ class ValuationBase(ModelSQL):
return abs(ratio) if ratio else Decimal(100)
@classmethod
def _previous_curve_price(cls, curve, price_date, unit, currency):
def _previous_curve_price(
cls, curve, price_date, unit, currency,
price_field='price_value'):
PriceValue = Pool().get('price.price_value')
previous = PriceValue.search([
('price', '=', curve.id),
@@ -764,13 +766,14 @@ class ValuationBase(ModelSQL):
if not previous:
return None
return Decimal(curve.get_price(
previous[0].price_date, unit, currency, last=False))
previous[0].price_date, unit, currency, last=False,
price_field=price_field))
@classmethod
def _curve_component_price(cls, component, line, strategy):
scenario = strategy.scenario
valuation_date = cls._strategy_valuation_date(strategy)
value = Decimal(component.price_index.get_price(
value = Decimal(component.get_price(
valuation_date,
line.unit,
strategy.currency,
@@ -779,7 +782,8 @@ class ValuationBase(ModelSQL):
component.price_index,
valuation_date,
line.unit,
strategy.currency)
strategy.currency,
price_field=getattr(component, 'curve_price_field', None))
return round(value, 4), (
round(previous, 4) if previous is not None else None)

View File

@@ -9,6 +9,9 @@
<newline/>
<label name="price_index"/>
<field name="price_index"/>
<label name="curve_price_field"/>
<field name="curve_price_field"/>
<newline/>
<label name="fixed_price"/>
<field name="fixed_price"/>
<label name="currency"/>

View File

@@ -9,6 +9,9 @@
<newline/>
<label name="price_index"/>
<field name="price_index"/>
<label name="curve_price_field"/>
<field name="curve_price_field"/>
<newline/>
<label name="fixed_price"/>
<field name="fixed_price"/>
<label name="currency"/>

View File

@@ -3,6 +3,7 @@
<field name="fix_type"/>
<field name="ratio" width="60"/>
<field name="price_index"/>
<field name="curve_price_field" width="80"/>
<field name="price_matrix"/>
<field name="fixed_price" width="80"/>
<field name="currency" width="60"/>

View File

@@ -3,6 +3,7 @@
<field name="fix_type"/>
<field name="ratio"/>
<field name="price_index"/>
<field name="curve_price_field"/>
<field name="price_matrix"/>
<field name="fixed_price"/>
<field name="currency"/>

View File

@@ -3,6 +3,7 @@
<field name="fix_type"/>
<field name="ratio"/>
<field name="price_index"/>
<field name="curve_price_field"/>
<field name="price_matrix"/>
<field name="fixed_price"/>
<field name="currency"/>