diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index f479236..82f9c8b 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -1182,11 +1182,12 @@ class ValuationDyn(metaclass=PoolMeta): Max(val.currency).as_('r_currency'), Sum(val.quantity).as_('r_quantity'), Max(val.unit).as_('r_unit'), - Sum(val.amount).as_('r_amount'), - Sum(val.base_amount).as_('r_base_amount'), - Sum(val.rate).as_('r_rate'), - Sum(val.mtm).as_('r_mtm'), - Max(val.strategy).as_('r_strategy'), + Sum(val.amount).as_('r_amount'), + Sum(val.base_amount).as_('r_base_amount'), + Sum(val.rate).as_('r_rate'), + Avg(val.mtm_price).as_('r_mtm_price'), + Sum(val.mtm).as_('r_mtm'), + Max(val.strategy).as_('r_strategy'), Max(val.lot).as_('r_lot'), Max(val.sale_line).as_('r_sale_line'), where=wh, diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 5a6d507..28d221b 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -71,6 +71,27 @@ class PurchaseTradeTestCase(ModuleTestCase): strategy.get_mtm(line, Decimal('10')), Decimal('250.00')) + def test_get_strategy_mtm_price_returns_unit_price(self): + 'strategy mtm price exposes the unit valuation price' + strategy = Mock( + scenario=Mock( + valuation_date='2026-03-29', + use_last_price=True, + ), + currency=Mock(), + ) + strategy.components = [Mock( + price_source_type='curve', + price_index=Mock(get_price=Mock(return_value=Decimal('100'))), + price_matrix=None, + ratio=Decimal('25'), + )] + line = Mock(unit=Mock()) + + self.assertEqual( + valuation_module.Valuation._get_strategy_mtm_price(strategy, line), + Decimal('25.0000')) + def test_parse_numbers_supports_inline_and_legacy_separators(self): 'parse_numbers keeps supporting inline entry and legacy separators' self.assertEqual( diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py index 137c789..97cf37c 100644 --- a/modules/purchase_trade/valuation.py +++ b/modules/purchase_trade/valuation.py @@ -56,6 +56,7 @@ class ValuationBase(ModelSQL): quantity = fields.Numeric("Quantity",digits=(16,5)) unit = fields.Many2One('product.uom',"Unit") amount = fields.Numeric("Amount",digits=(16,2)) + mtm_price = fields.Numeric("Mtm Price", digits=(16,4)) mtm = fields.Numeric("Mtm",digits=(16,2)) strategy = fields.Many2One('mtm.strategy',"Strategy") lot = fields.Many2One('lot.lot',"Lot") @@ -121,6 +122,34 @@ class ValuationBase(ModelSQL): values['sale'] = sale.id return values + + @classmethod + def _get_strategy_mtm_price(cls, strategy, line): + total = Decimal(0) + scenario = getattr(strategy, 'scenario', None) + if not scenario: + return None + + for comp in strategy.components or []: + value = Decimal(0) + + if comp.price_source_type == 'curve' and comp.price_index: + value = Decimal(comp.price_index.get_price( + scenario.valuation_date, + line.unit, + strategy.currency, + last=scenario.use_last_price + )) + elif comp.price_source_type == 'matrix' and comp.price_matrix: + value = Decimal(strategy._get_matrix_price( + comp, line, scenario.valuation_date)) + + if comp.ratio: + value *= Decimal(comp.ratio) / Decimal(100) + + total += value + + return round(total, 4) @classmethod def _build_basis_pnl(cls, *, line, lot, sale_line, pc, sign): @@ -175,6 +204,7 @@ class ValuationBase(ModelSQL): 'amount': amount, 'base_amount': base_amount, 'rate': rate, + 'mtm_price': None, 'mtm': None, #round(amount - (mtm * pc.ratio / 100), 2), 'unit': sale_line.unit.id if sale_line else line.unit.id, 'currency': currency, @@ -211,6 +241,7 @@ class ValuationBase(ModelSQL): 'amount': amount, 'base_amount': base_amount, 'rate': rate, + 'mtm_price': None, 'mtm': Decimal(0), 'state': state, 'unit': sale_line.unit.id if sale_line else line.unit.id, @@ -238,6 +269,7 @@ class ValuationBase(ModelSQL): values = cls._build_basis_pnl(line=line, lot=lot, sale_line=None, pc=pc, sign=-1) if line.mtm: for strat in line.mtm: + values['mtm_price'] = cls._get_strategy_mtm_price(strat, line) values['mtm'] = strat.get_mtm(line,values['quantity']) values['strategy'] = strat @@ -259,6 +291,7 @@ class ValuationBase(ModelSQL): ) if line.mtm: for strat in line.mtm: + values['mtm_price'] = cls._get_strategy_mtm_price(strat, line) values['mtm'] = strat.get_mtm(line,values['quantity']) values['strategy'] = strat @@ -286,6 +319,7 @@ class ValuationBase(ModelSQL): values = cls._build_basis_pnl(line=line, lot=sl, sale_line=sl_line, pc=pc, sign=+1) if sl_line.mtm: for strat in line.mtm: + values['mtm_price'] = cls._get_strategy_mtm_price(strat, sl_line) values['mtm'] = strat.get_mtm(sl_line,values['quantity']) values['strategy'] = strat @@ -307,6 +341,7 @@ class ValuationBase(ModelSQL): ) if sl_line.mtm: for strat in sl_line.mtm: + values['mtm_price'] = cls._get_strategy_mtm_price(strat, sl_line) values['mtm'] = strat.get_mtm(sl_line,values['quantity']) values['strategy'] = strat @@ -388,6 +423,7 @@ class ValuationBase(ModelSQL): 'state': sf.type, 'quantity': qty, 'amount': amount, + 'mtm_price': cls._get_strategy_mtm_price(strat, line), 'mtm': strat.get_mtm(line,qty), 'strategy': strat, 'unit': sf.unit.id if sf.unit else line.unit.id, @@ -412,6 +448,7 @@ class ValuationBase(ModelSQL): 'state': sf.type, 'quantity': qty, 'amount': amount, + 'mtm_price': None, 'mtm': Decimal(0), 'strategy': None, 'unit': sf.unit.id if sf.unit else line.unit.id, @@ -430,7 +467,7 @@ class ValuationBase(ModelSQL): d.price, line.unit, line.purchase.currency )) - mtm = Decimal(d.price_index.get_price( + mtm_price = Decimal(d.price_index.get_price( Date.today(), line.unit, line.purchase.currency, True )) @@ -446,7 +483,8 @@ class ValuationBase(ModelSQL): 'state': 'fixed', 'quantity': round(d.quantity, 5), 'amount': round(price * d.quantity * Decimal(-1), 2), - 'mtm': round((price * d.quantity * Decimal(-1)) - (mtm * d.quantity * Decimal(-1)), 2), + 'mtm_price': round(mtm_price, 4), + 'mtm': round((price * d.quantity * Decimal(-1)) - (mtm_price * d.quantity * Decimal(-1)), 2), 'unit': line.unit.id, 'currency': line.purchase.currency.id, }) @@ -535,6 +573,7 @@ class ValuationDyn(ModelSQL,ModelView): r_amount = fields.Numeric("Amount",digits='r_unit') r_base_amount = fields.Numeric("Base Amount",digits='r_unit') r_rate = fields.Numeric("Rate",digits=(16,6)) + r_mtm_price = fields.Numeric("Mtm Price",digits='r_unit') r_mtm = fields.Numeric("Mtm",digits='r_unit') r_strategy = fields.Many2One('mtm.strategy',"Strategy") r_lot = fields.Many2One('lot.lot',"Lot") @@ -568,6 +607,7 @@ class ValuationDyn(ModelSQL,ModelView): Sum(val.amount).as_('r_amount'), Sum(val.base_amount).as_('r_base_amount'), Sum(val.rate).as_('r_rate'), + Avg(val.mtm_price).as_('r_mtm_price'), Sum(val.mtm).as_('r_mtm'), Max(val.strategy).as_('r_strategy'), Max(val.lot).as_('r_lot'), @@ -617,6 +657,7 @@ class ValuationReport(ValuationBase, ModelView): val.amount.as_('amount'), val.base_amount.as_('base_amount'), val.rate.as_('rate'), + val.mtm_price.as_('mtm_price'), val.mtm.as_('mtm'), val.strategy.as_('strategy'), val.lot.as_('lot'), diff --git a/modules/purchase_trade/view/valuation_list.xml b/modules/purchase_trade/view/valuation_list.xml index 57fc4eb..6c946bb 100644 --- a/modules/purchase_trade/view/valuation_list.xml +++ b/modules/purchase_trade/view/valuation_list.xml @@ -12,5 +12,6 @@ + - \ No newline at end of file + diff --git a/modules/purchase_trade/view/valuation_tree_sequence3.xml b/modules/purchase_trade/view/valuation_tree_sequence3.xml index 0d64ced..4cc5caf 100755 --- a/modules/purchase_trade/view/valuation_tree_sequence3.xml +++ b/modules/purchase_trade/view/valuation_tree_sequence3.xml @@ -11,5 +11,6 @@ this repository contains the full copyright notices and license terms. --> + - \ No newline at end of file + diff --git a/modules/purchase_trade/view/valuation_tree_sequence4.xml b/modules/purchase_trade/view/valuation_tree_sequence4.xml index 1580a59..a7a87bf 100755 --- a/modules/purchase_trade/view/valuation_tree_sequence4.xml +++ b/modules/purchase_trade/view/valuation_tree_sequence4.xml @@ -11,6 +11,7 @@ this repository contains the full copyright notices and license terms. --> +