Pricing low mid high
This commit is contained in:
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user