Pnl
This commit is contained in:
@@ -2069,6 +2069,96 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
|||||||
{'type': 'derivative'},
|
{'type': 'derivative'},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_purchase_derivative_pnl_uses_direction_and_market_delta(self):
|
||||||
|
'purchase derivative pnl is entry versus market with long short direction'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
today = datetime.date(2026, 7, 16)
|
||||||
|
currency = Mock(id=1)
|
||||||
|
company_currency = Mock(id=2)
|
||||||
|
unit = Mock(id=3)
|
||||||
|
price_index = Mock(
|
||||||
|
price_index='ICE',
|
||||||
|
get_price_per_qt=Mock(return_value=Decimal('100')),
|
||||||
|
get_price=Mock(return_value=Decimal('80')))
|
||||||
|
party = Mock(id=4)
|
||||||
|
product = Mock(id=5)
|
||||||
|
purchase = Mock(
|
||||||
|
id=6,
|
||||||
|
currency=currency,
|
||||||
|
company=Mock(currency=company_currency))
|
||||||
|
line = Mock(id=7, unit=unit, purchase=purchase)
|
||||||
|
|
||||||
|
def derivative(direction):
|
||||||
|
return Mock(
|
||||||
|
price_index=price_index,
|
||||||
|
price=Decimal('100'),
|
||||||
|
direction=direction,
|
||||||
|
quantity=Decimal('10'),
|
||||||
|
party=party,
|
||||||
|
product=product)
|
||||||
|
|
||||||
|
line.derivatives = [derivative('long'), derivative('short')]
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock, patch.object(
|
||||||
|
Valuation, '_base_amount_values',
|
||||||
|
side_effect=[
|
||||||
|
(Decimal('-200'), Decimal('1'), 2),
|
||||||
|
(Decimal('200'), Decimal('1'), 2),
|
||||||
|
]):
|
||||||
|
PoolMock.return_value.get.return_value = Mock(
|
||||||
|
today=Mock(return_value=today))
|
||||||
|
values = Valuation.create_pnl_der_from_line(line)
|
||||||
|
|
||||||
|
self.assertEqual(values[0]['amount'], Decimal('-200.00'))
|
||||||
|
self.assertEqual(values[0]['base_amount'], Decimal('-200'))
|
||||||
|
self.assertIsNone(values[0]['mtm_price'])
|
||||||
|
self.assertIsNone(values[0]['mtm'])
|
||||||
|
self.assertEqual(values[1]['amount'], Decimal('200.00'))
|
||||||
|
self.assertEqual(values[1]['base_amount'], Decimal('200'))
|
||||||
|
self.assertIsNone(values[1]['mtm_price'])
|
||||||
|
self.assertIsNone(values[1]['mtm'])
|
||||||
|
|
||||||
|
def test_sale_derivative_pnl_uses_direction_and_market_delta(self):
|
||||||
|
'sale derivative pnl is stored in amount and base amount only'
|
||||||
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
today = datetime.date(2026, 7, 16)
|
||||||
|
currency = Mock(id=1)
|
||||||
|
company_currency = Mock(id=2)
|
||||||
|
unit = Mock(id=3)
|
||||||
|
price_index = Mock(
|
||||||
|
price_index='ICE',
|
||||||
|
get_price_per_qt=Mock(return_value=Decimal('100')),
|
||||||
|
get_price=Mock(return_value=Decimal('120')))
|
||||||
|
derivative = Mock(
|
||||||
|
price_index=price_index,
|
||||||
|
price=Decimal('100'),
|
||||||
|
direction='long',
|
||||||
|
quantity=Decimal('10'),
|
||||||
|
party=Mock(id=4),
|
||||||
|
product=Mock(id=5))
|
||||||
|
sale = Mock(
|
||||||
|
id=6,
|
||||||
|
currency=currency,
|
||||||
|
company=Mock(currency=company_currency))
|
||||||
|
sale_line = Mock(
|
||||||
|
id=7,
|
||||||
|
unit=unit,
|
||||||
|
sale=sale,
|
||||||
|
derivatives=[derivative])
|
||||||
|
|
||||||
|
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock, patch.object(
|
||||||
|
Valuation, '_base_amount_values',
|
||||||
|
return_value=(Decimal('200'), Decimal('1'), 2)):
|
||||||
|
PoolMock.return_value.get.return_value = Mock(
|
||||||
|
today=Mock(return_value=today))
|
||||||
|
values = Valuation.create_pnl_der_from_sale_line(sale_line)
|
||||||
|
|
||||||
|
self.assertEqual(values[0]['price'], Decimal('100.0000'))
|
||||||
|
self.assertEqual(values[0]['amount'], Decimal('200.00'))
|
||||||
|
self.assertEqual(values[0]['base_amount'], Decimal('200'))
|
||||||
|
self.assertIsNone(values[0]['mtm_price'])
|
||||||
|
self.assertIsNone(values[0]['mtm'])
|
||||||
|
|
||||||
def test_update_daily_snapshot_generates_all_purchases_and_unmatched_sales(self):
|
def test_update_daily_snapshot_generates_all_purchases_and_unmatched_sales(self):
|
||||||
'daily valuation cron snapshots purchases and unmatched sale lines'
|
'daily valuation cron snapshots purchases and unmatched sale lines'
|
||||||
Valuation = Pool().get('valuation.valuation')
|
Valuation = Pool().get('valuation.valuation')
|
||||||
|
|||||||
@@ -1596,17 +1596,14 @@ class ValuationBase(ModelSQL):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_pnl_der_from_line(cls, line):
|
def create_pnl_der_from_line(cls, line):
|
||||||
Date = Pool().get('ir.date')
|
|
||||||
der_lines = []
|
der_lines = []
|
||||||
|
|
||||||
for d in line.derivatives or []:
|
for d in line.derivatives or []:
|
||||||
price = Decimal(d.price_index.get_price_per_qt(
|
currency = line.purchase.currency
|
||||||
d.price, line.unit, line.purchase.currency
|
price, _market_price, amount = cls._derivative_pnl_values(
|
||||||
))
|
d, line.unit, currency)
|
||||||
|
base_amount, rate, base_currency = cls._base_amount_values(
|
||||||
mtm_price = Decimal(d.price_index.get_price(
|
amount, currency, line.purchase.company.currency)
|
||||||
Date.today(), line.unit, line.purchase.currency, True
|
|
||||||
))
|
|
||||||
|
|
||||||
der_lines.append({
|
der_lines.append({
|
||||||
'purchase': line.purchase.id,
|
'purchase': line.purchase.id,
|
||||||
@@ -1619,28 +1616,28 @@ class ValuationBase(ModelSQL):
|
|||||||
'product': d.product.id,
|
'product': d.product.id,
|
||||||
'state': 'fixed',
|
'state': 'fixed',
|
||||||
'quantity': round(d.quantity, 5),
|
'quantity': round(d.quantity, 5),
|
||||||
'amount': round(price * d.quantity * Decimal(-1), 2),
|
'amount': amount,
|
||||||
'mtm_price': round(mtm_price, 4),
|
'base_amount': base_amount,
|
||||||
'mtm': round((price * d.quantity * Decimal(-1)) - (mtm_price * d.quantity * Decimal(-1)), 2),
|
'base_currency': base_currency,
|
||||||
|
'rate': rate,
|
||||||
|
'mtm_price': None,
|
||||||
|
'mtm': None,
|
||||||
'unit': line.unit.id,
|
'unit': line.unit.id,
|
||||||
'currency': line.purchase.currency.id,
|
'currency': currency.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return der_lines
|
return der_lines
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_pnl_der_from_sale_line(cls, sale_line):
|
def create_pnl_der_from_sale_line(cls, sale_line):
|
||||||
Date = Pool().get('ir.date')
|
|
||||||
der_lines = []
|
der_lines = []
|
||||||
|
|
||||||
for d in sale_line.derivatives or []:
|
for d in sale_line.derivatives or []:
|
||||||
price = Decimal(d.price_index.get_price_per_qt(
|
currency = sale_line.sale.currency
|
||||||
d.price, sale_line.unit, sale_line.sale.currency
|
price, _market_price, amount = cls._derivative_pnl_values(
|
||||||
))
|
d, sale_line.unit, currency)
|
||||||
|
base_amount, rate, base_currency = cls._base_amount_values(
|
||||||
mtm_price = Decimal(d.price_index.get_price(
|
amount, currency, sale_line.sale.company.currency)
|
||||||
Date.today(), sale_line.unit, sale_line.sale.currency, True
|
|
||||||
))
|
|
||||||
|
|
||||||
der_lines.append({
|
der_lines.append({
|
||||||
'sale': sale_line.sale.id,
|
'sale': sale_line.sale.id,
|
||||||
@@ -1653,14 +1650,31 @@ class ValuationBase(ModelSQL):
|
|||||||
'product': d.product.id,
|
'product': d.product.id,
|
||||||
'state': 'fixed',
|
'state': 'fixed',
|
||||||
'quantity': round(d.quantity, 5),
|
'quantity': round(d.quantity, 5),
|
||||||
'amount': round(price * d.quantity * Decimal(-1), 2),
|
'amount': amount,
|
||||||
'mtm_price': round(mtm_price, 4),
|
'base_amount': base_amount,
|
||||||
'mtm': round((price * d.quantity * Decimal(-1)) - (mtm_price * d.quantity * Decimal(-1)), 2),
|
'base_currency': base_currency,
|
||||||
|
'rate': rate,
|
||||||
|
'mtm_price': None,
|
||||||
|
'mtm': None,
|
||||||
'unit': sale_line.unit.id,
|
'unit': sale_line.unit.id,
|
||||||
'currency': sale_line.sale.currency.id,
|
'currency': currency.id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return der_lines
|
return der_lines
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _derivative_pnl_values(cls, derivative, unit, currency):
|
||||||
|
Date = Pool().get('ir.date')
|
||||||
|
entry_price = Decimal(derivative.price_index.get_price_per_qt(
|
||||||
|
derivative.price, unit, currency))
|
||||||
|
market_price = Decimal(derivative.price_index.get_price(
|
||||||
|
Date.today(), unit, currency, True))
|
||||||
|
quantity = Decimal(derivative.quantity or 0)
|
||||||
|
direction = Decimal(1)
|
||||||
|
if getattr(derivative, 'direction', None) == 'short':
|
||||||
|
direction = Decimal(-1)
|
||||||
|
amount = round((market_price - entry_price) * quantity * direction, 2)
|
||||||
|
return round(entry_price, 4), round(market_price, 4), amount
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate(cls, line, valuation_type='all'):
|
def generate(cls, line, valuation_type='all'):
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<group id="currencies_links" string="Currencies & Links"
|
<group id="currencies_links" string="Currencies & Links"
|
||||||
colspan="1" col="4" panel="card" icon="tryton-link"
|
colspan="1" col="4" panel="card" icon="tryton-link"
|
||||||
xalign="0" yalign="0"
|
xalign="0" yalign="0"
|
||||||
col_widths="min-content,min-content,min-content,1fr">
|
col_widths="min-content,1fr,min-content,1fr">
|
||||||
<label name="enable_linked_currency"/>
|
<label name="enable_linked_currency"/>
|
||||||
<field name="enable_linked_currency" xexpand="0" xfill="0"/>
|
<field name="enable_linked_currency" xexpand="0" xfill="0"/>
|
||||||
<label name="linked_currency"/>
|
<label name="linked_currency"/>
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||||||
<group id="currencies_links" string="Currencies & Links"
|
<group id="currencies_links" string="Currencies & Links"
|
||||||
colspan="1" col="4" panel="card" icon="tryton-link"
|
colspan="1" col="4" panel="card" icon="tryton-link"
|
||||||
xalign="0" yalign="0"
|
xalign="0" yalign="0"
|
||||||
col_widths="min-content,min-content,min-content,1fr">
|
col_widths="min-content,1fr,min-content,1fr">
|
||||||
<label name="enable_linked_currency"/>
|
<label name="enable_linked_currency"/>
|
||||||
<field name="enable_linked_currency" xexpand="0" xfill="0"/>
|
<field name="enable_linked_currency" xexpand="0" xfill="0"/>
|
||||||
<label name="linked_currency"/>
|
<label name="linked_currency"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user