Pnl
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
|
~$*
|
||||||
notes/accounting/excel_web_api/.env
|
notes/accounting/excel_web_api/.env
|
||||||
|
|||||||
@@ -160,6 +160,62 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
strategy, line),
|
strategy, line),
|
||||||
Decimal('-349167.53'))
|
Decimal('-349167.53'))
|
||||||
|
|
||||||
|
@with_transaction()
|
||||||
|
def test_strategy_mtm_lines_split_by_curve_with_previous_price(self):
|
||||||
|
'strategy MTM creates one valuation line per curve with previous price'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
valuation_date = datetime.date(2026, 6, 5)
|
||||||
|
previous_date = datetime.date(2026, 6, 4)
|
||||||
|
unit = Mock()
|
||||||
|
currency = Mock()
|
||||||
|
line = Mock(unit=unit)
|
||||||
|
curve_a = Mock(id=10)
|
||||||
|
curve_a.get_price.side_effect = [Decimal('100'), Decimal('90')]
|
||||||
|
curve_b = Mock(id=20)
|
||||||
|
curve_b.get_price.side_effect = [Decimal('50'), Decimal('45')]
|
||||||
|
strategy = Mock(
|
||||||
|
scenario=Mock(
|
||||||
|
valuation_date=valuation_date,
|
||||||
|
use_last_price=False),
|
||||||
|
currency=currency,
|
||||||
|
components=[
|
||||||
|
Mock(
|
||||||
|
price_source_type='curve',
|
||||||
|
price_index=curve_a,
|
||||||
|
ratio=Decimal('60')),
|
||||||
|
Mock(
|
||||||
|
price_source_type='curve',
|
||||||
|
price_index=curve_b,
|
||||||
|
ratio=Decimal('40')),
|
||||||
|
])
|
||||||
|
values = {
|
||||||
|
'type': 'pur. priced',
|
||||||
|
'price': Decimal('10'),
|
||||||
|
'amount': Decimal('-100'),
|
||||||
|
'base_amount': Decimal('-100'),
|
||||||
|
'quantity': Decimal('10'),
|
||||||
|
}
|
||||||
|
target = []
|
||||||
|
price_value = Mock()
|
||||||
|
price_value.search.return_value = [Mock(price_date=previous_date)]
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
|
||||||
|
PoolMock.return_value.get.return_value = price_value
|
||||||
|
Valuation._append_strategy_mtm_lines(
|
||||||
|
target, values, strategy, line)
|
||||||
|
|
||||||
|
self.assertEqual(len(target), 2)
|
||||||
|
self.assertEqual(target[0]['mtm_curve'], curve_a.id)
|
||||||
|
self.assertEqual(target[0]['amount'], Decimal('-60.00'))
|
||||||
|
self.assertEqual(target[0]['mtm_price'], Decimal('60.0000'))
|
||||||
|
self.assertEqual(target[0]['mtm_price_prev'], Decimal('54.0000'))
|
||||||
|
self.assertEqual(target[0]['mtm'], Decimal('-600.00'))
|
||||||
|
self.assertEqual(target[1]['mtm_curve'], curve_b.id)
|
||||||
|
self.assertEqual(target[1]['amount'], Decimal('-40.00'))
|
||||||
|
self.assertEqual(target[1]['mtm_price'], Decimal('20.0000'))
|
||||||
|
self.assertEqual(target[1]['mtm_price_prev'], Decimal('18.0000'))
|
||||||
|
self.assertEqual(target[1]['mtm'], Decimal('-200.00'))
|
||||||
|
|
||||||
@with_transaction()
|
@with_transaction()
|
||||||
def test_purchase_line_charter_conditions_inherit_header_when_empty(self):
|
def test_purchase_line_charter_conditions_inherit_header_when_empty(self):
|
||||||
'purchase line uses header charter conditions when it has no line terms'
|
'purchase line uses header charter conditions when it has no line terms'
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ class ValuationBase(ModelSQL):
|
|||||||
unit = fields.Many2One('product.uom',"Unit")
|
unit = fields.Many2One('product.uom',"Unit")
|
||||||
amount = fields.Numeric("Amount",digits=(16,2))
|
amount = fields.Numeric("Amount",digits=(16,2))
|
||||||
mtm_price = fields.Numeric("Mtm Price", digits=(16,4))
|
mtm_price = fields.Numeric("Mtm Price", digits=(16,4))
|
||||||
|
mtm_price_prev = fields.Numeric("Mtm Price -1", digits=(16,4))
|
||||||
|
mtm_curve = fields.Many2One('price.price', "Curve")
|
||||||
mtm = fields.Numeric("Mtm",digits=(16,2))
|
mtm = fields.Numeric("Mtm",digits=(16,2))
|
||||||
strategy = fields.Many2One('mtm.strategy',"Strategy")
|
strategy = fields.Many2One('mtm.strategy',"Strategy")
|
||||||
lot = fields.Many2One('lot.lot',"Lot")
|
lot = fields.Many2One('lot.lot',"Lot")
|
||||||
@@ -387,6 +389,122 @@ class ValuationBase(ModelSQL):
|
|||||||
return -abs(mtm)
|
return -abs(mtm)
|
||||||
return abs(mtm)
|
return abs(mtm)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _component_weight(component):
|
||||||
|
ratio = Decimal(component.ratio or 0)
|
||||||
|
return abs(ratio) if ratio else Decimal(100)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _previous_curve_price(cls, curve, price_date, unit, currency):
|
||||||
|
PriceValue = Pool().get('price.price_value')
|
||||||
|
previous = PriceValue.search([
|
||||||
|
('price', '=', curve.id),
|
||||||
|
('price_date', '<', price_date),
|
||||||
|
], order=[('price_date', 'DESC')], limit=1)
|
||||||
|
if not previous:
|
||||||
|
return None
|
||||||
|
return Decimal(curve.get_price(
|
||||||
|
previous[0].price_date, unit, currency, last=False))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _curve_component_price(cls, component, line, strategy):
|
||||||
|
scenario = strategy.scenario
|
||||||
|
value = Decimal(component.price_index.get_price(
|
||||||
|
scenario.valuation_date,
|
||||||
|
line.unit,
|
||||||
|
strategy.currency,
|
||||||
|
last=scenario.use_last_price))
|
||||||
|
previous = cls._previous_curve_price(
|
||||||
|
component.price_index,
|
||||||
|
scenario.valuation_date,
|
||||||
|
line.unit,
|
||||||
|
strategy.currency)
|
||||||
|
if component.ratio:
|
||||||
|
ratio = Decimal(component.ratio) / Decimal(100)
|
||||||
|
value *= ratio
|
||||||
|
if previous is not None:
|
||||||
|
previous *= ratio
|
||||||
|
return round(value, 4), (
|
||||||
|
round(previous, 4) if previous is not None else None)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _split_value(cls, value, share, digits=2):
|
||||||
|
if value in (None, ''):
|
||||||
|
return value
|
||||||
|
return round(Decimal(value) * share, digits)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _append_strategy_mtm_lines(cls, target, values, strategy, line):
|
||||||
|
components = list(strategy.components or [])
|
||||||
|
curve_components = [
|
||||||
|
component for component in components
|
||||||
|
if component.price_source_type == 'curve'
|
||||||
|
and component.price_index]
|
||||||
|
if not curve_components:
|
||||||
|
line_values = dict(values)
|
||||||
|
line_values['mtm_price'] = cls._get_strategy_mtm_price(
|
||||||
|
strategy, line)
|
||||||
|
line_values['mtm'] = cls._signed_strategy_mtm(
|
||||||
|
values, strategy, line)
|
||||||
|
line_values['strategy'] = strategy
|
||||||
|
target.append(line_values)
|
||||||
|
return
|
||||||
|
|
||||||
|
curve_component_ids = {id(component) for component in curve_components}
|
||||||
|
total_weight = sum(
|
||||||
|
cls._component_weight(component)
|
||||||
|
for component in components) or Decimal(100)
|
||||||
|
total_mtm_price = cls._get_strategy_mtm_price(strategy, line)
|
||||||
|
curve_mtm_price = Decimal(0)
|
||||||
|
signed_factor = Decimal(1)
|
||||||
|
amount = values.get('amount')
|
||||||
|
if amount and amount < 0:
|
||||||
|
signed_factor = Decimal(-1)
|
||||||
|
elif not amount and values.get('type') in {'pur. priced', 'pur. efp'}:
|
||||||
|
signed_factor = Decimal(-1)
|
||||||
|
|
||||||
|
for component in curve_components:
|
||||||
|
share = cls._component_weight(component) / total_weight
|
||||||
|
mtm_price, mtm_price_prev = cls._curve_component_price(
|
||||||
|
component, line, strategy)
|
||||||
|
curve_mtm_price += mtm_price
|
||||||
|
line_values = dict(values)
|
||||||
|
line_values['price'] = cls._split_value(
|
||||||
|
values.get('price'), share, digits=4)
|
||||||
|
line_values['amount'] = cls._split_value(
|
||||||
|
values.get('amount'), share)
|
||||||
|
line_values['base_amount'] = cls._split_value(
|
||||||
|
values.get('base_amount'), share)
|
||||||
|
line_values['mtm_price'] = mtm_price
|
||||||
|
line_values['mtm_price_prev'] = mtm_price_prev
|
||||||
|
line_values['mtm_curve'] = component.price_index.id
|
||||||
|
line_values['mtm'] = round(
|
||||||
|
mtm_price * Decimal(values['quantity']) * signed_factor, 2)
|
||||||
|
line_values['strategy'] = strategy
|
||||||
|
target.append(line_values)
|
||||||
|
|
||||||
|
residual_weight = sum(
|
||||||
|
cls._component_weight(component)
|
||||||
|
for component in components
|
||||||
|
if id(component) not in curve_component_ids)
|
||||||
|
if residual_weight:
|
||||||
|
share = residual_weight / total_weight
|
||||||
|
mtm_price = round(total_mtm_price - curve_mtm_price, 4)
|
||||||
|
line_values = dict(values)
|
||||||
|
line_values['price'] = cls._split_value(
|
||||||
|
values.get('price'), share, digits=4)
|
||||||
|
line_values['amount'] = cls._split_value(
|
||||||
|
values.get('amount'), share)
|
||||||
|
line_values['base_amount'] = cls._split_value(
|
||||||
|
values.get('base_amount'), share)
|
||||||
|
line_values['mtm_price'] = mtm_price
|
||||||
|
line_values['mtm_price_prev'] = None
|
||||||
|
line_values['mtm_curve'] = None
|
||||||
|
line_values['mtm'] = round(
|
||||||
|
mtm_price * Decimal(values['quantity']) * signed_factor, 2)
|
||||||
|
line_values['strategy'] = strategy
|
||||||
|
target.append(line_values)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_basis_component_total(record):
|
def _get_basis_component_total(record):
|
||||||
getter = getattr(record, '_get_basis_component_price', None)
|
getter = getattr(record, '_get_basis_component_price', None)
|
||||||
@@ -537,12 +655,8 @@ class ValuationBase(ModelSQL):
|
|||||||
cls._set_matched_sale_values(values, matched_sale_line)
|
cls._set_matched_sale_values(values, matched_sale_line)
|
||||||
if line.mtm and cls._supports_strategy_mtm(values):
|
if line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in line.mtm:
|
for strat in line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, line)
|
price_lines, values, strat, line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -555,12 +669,8 @@ class ValuationBase(ModelSQL):
|
|||||||
cls._set_matched_sale_values(values, matched_sale_line)
|
cls._set_matched_sale_values(values, matched_sale_line)
|
||||||
if line.mtm and cls._supports_strategy_mtm(values):
|
if line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in line.mtm:
|
for strat in line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, line)
|
price_lines, values, strat, line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -578,12 +688,8 @@ class ValuationBase(ModelSQL):
|
|||||||
cls._set_matched_sale_values(values, matched_sale_line)
|
cls._set_matched_sale_values(values, matched_sale_line)
|
||||||
if line.mtm and cls._supports_strategy_mtm(values):
|
if line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in line.mtm:
|
for strat in line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, line)
|
price_lines, values, strat, line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -613,12 +719,8 @@ class ValuationBase(ModelSQL):
|
|||||||
extra_price=premium_delta)
|
extra_price=premium_delta)
|
||||||
if sl_line.mtm and cls._supports_strategy_mtm(values):
|
if sl_line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in line.mtm:
|
for strat in line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, sl_line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, sl_line)
|
price_lines, values, strat, sl_line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -635,12 +737,8 @@ class ValuationBase(ModelSQL):
|
|||||||
)
|
)
|
||||||
if sl_line.mtm and cls._supports_strategy_mtm(values):
|
if sl_line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in sl_line.mtm:
|
for strat in sl_line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, sl_line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, sl_line)
|
price_lines, values, strat, sl_line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -761,12 +859,8 @@ class ValuationBase(ModelSQL):
|
|||||||
)
|
)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in sale_line.mtm:
|
for strat in sale_line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, sale_line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, sale_line)
|
price_lines, values, strat, sale_line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -778,12 +872,8 @@ class ValuationBase(ModelSQL):
|
|||||||
extra_price=premium_delta)
|
extra_price=premium_delta)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in sale_line.mtm:
|
for strat in sale_line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, sale_line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, sale_line)
|
price_lines, values, strat, sale_line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
@@ -801,12 +891,8 @@ class ValuationBase(ModelSQL):
|
|||||||
)
|
)
|
||||||
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
if sale_line.mtm and cls._supports_strategy_mtm(values):
|
||||||
for strat in sale_line.mtm:
|
for strat in sale_line.mtm:
|
||||||
values['mtm_price'] = cls._get_strategy_mtm_price(strat, sale_line)
|
cls._append_strategy_mtm_lines(
|
||||||
values['mtm'] = cls._signed_strategy_mtm(values, strat, sale_line)
|
price_lines, values, strat, sale_line)
|
||||||
values['strategy'] = strat
|
|
||||||
|
|
||||||
if values:
|
|
||||||
price_lines.append(values)
|
|
||||||
else:
|
else:
|
||||||
if values:
|
if values:
|
||||||
price_lines.append(values)
|
price_lines.append(values)
|
||||||
|
|||||||
@@ -14,6 +14,12 @@
|
|||||||
<field name="base_amount" sum="1"/>
|
<field name="base_amount" sum="1"/>
|
||||||
<field name="rate"/>
|
<field name="rate"/>
|
||||||
<field name="strategy"/>
|
<field name="strategy"/>
|
||||||
<field name="mtm_price"/>
|
<field name="mtm_curve"/>
|
||||||
|
<field name="mtm_price"
|
||||||
|
widget="variation"
|
||||||
|
variation_previous="mtm_price_prev"
|
||||||
|
variation_group_by="mtm_curve"
|
||||||
|
variation_summary="1"/>
|
||||||
|
<field name="mtm_price_prev" optional="1"/>
|
||||||
<field name="mtm" optional="0" sum="1"/>
|
<field name="mtm" optional="0" sum="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
@@ -13,6 +13,12 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<field name="quantity" symbol="unit"/>
|
<field name="quantity" symbol="unit"/>
|
||||||
<field name="amount" sum="1"/>
|
<field name="amount" sum="1"/>
|
||||||
<field name="strategy"/>
|
<field name="strategy"/>
|
||||||
<field name="mtm_price"/>
|
<field name="mtm_curve"/>
|
||||||
|
<field name="mtm_price"
|
||||||
|
widget="variation"
|
||||||
|
variation_previous="mtm_price_prev"
|
||||||
|
variation_group_by="mtm_curve"
|
||||||
|
variation_summary="1"/>
|
||||||
|
<field name="mtm_price_prev" optional="1"/>
|
||||||
<field name="mtm" optional="0" sum="1"/>
|
<field name="mtm" optional="0" sum="1"/>
|
||||||
</tree>
|
</tree>
|
||||||
|
|||||||
Reference in New Issue
Block a user