diff --git a/modules/price/price.py b/modules/price/price.py index 651fbb3..da0f087 100755 --- a/modules/price/price.py +++ b/modules/price/price.py @@ -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): diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index 8674498..768c178 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -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) diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 9a5ee16..fed59a6 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -6060,7 +6060,27 @@ description 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 ('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') diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py index 1d63b2c..c73bd43 100644 --- a/modules/purchase_trade/valuation.py +++ b/modules/purchase_trade/valuation.py @@ -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) diff --git a/modules/purchase_trade/view/component_form.xml b/modules/purchase_trade/view/component_form.xml index 0410909..f795ab2 100755 --- a/modules/purchase_trade/view/component_form.xml +++ b/modules/purchase_trade/view/component_form.xml @@ -9,6 +9,9 @@