diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index bd8fbc9..85b32de 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -172,6 +172,11 @@ def register(): forex.ForexCategory, pricing.Component, pricing.Mtm, + pricing.MtmStrategy, + pricing.MtmScenario, + pricing.MtmSnapshot, + pricing.PriceMatrix, + pricing.PriceMatrixLine, pricing.Estimated, pricing.Pricing, pricing.Period, diff --git a/modules/purchase_trade/icons/tradon-mtm.svg b/modules/purchase_trade/icons/tradon-mtm.svg new file mode 100644 index 0000000..c5c7214 --- /dev/null +++ b/modules/purchase_trade/icons/tradon-mtm.svg @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/modules/purchase_trade/pricing.py b/modules/purchase_trade/pricing.py index a346d2d..a5d911e 100755 --- a/modules/purchase_trade/pricing.py +++ b/modules/purchase_trade/pricing.py @@ -50,8 +50,6 @@ DAYS = [ ('sunday', 'Sunday'), ] - - class Estimated(ModelSQL, ModelView): "Estimated date" __name__ = 'pricing.estimated' @@ -59,21 +57,212 @@ class Estimated(ModelSQL, ModelView): trigger = fields.Selection(TRIGGERS,"Trigger") estimated_date = fields.Date("Estimated date") +class MtmScenario(ModelSQL, ModelView): + "MtM Scenario" + __name__ = 'mtm.scenario' + + name = fields.Char("Scenario", required=True) + valuation_date = fields.Date("Valuation Date", required=True) + use_last_price = fields.Boolean("Use Last Available Price") + calendar = fields.Many2One( + 'price.calendar', "Calendar" + ) + +class MtmStrategy(ModelSQL, ModelView): + "Mark to Market Strategy" + __name__ = 'mtm.strategy' + + name = fields.Char("Name", required=True) + active = fields.Boolean("Active", select=True) + + scenario = fields.Many2One( + 'mtm.scenario', "Scenario", required=True + ) + + currency = fields.Many2One( + 'currency.currency', "Valuation Currency" + ) + + components = fields.One2Many( + 'mtm.component', 'strategy', "Components" + ) + + def compute_mtm(self): + pool = Pool() + Currency = pool.get('currency.currency') + total = Decimal(0) + + scenario = self.scenario + dt = scenario.valuation_date + + for comp in self.components: + value = Decimal(0) + + if comp.price_source_type == 'curve' and comp.price_curve: + value = Decimal( + comp.price_curve.get_price( + dt, + self.purchase_line.unit, + self.currency, + last=scenario.use_last_price + ) + ) + + elif comp.price_source_type == 'matrix' and comp.price_matrix: + value = self._get_matrix_price(comp, dt) + + if comp.ratio: + value *= Decimal(comp.ratio) + + total += value + + return total + + def _get_matrix_price(self, comp, dt): + MatrixLine = Pool().get('price.matrix.line') + + domain = [ + ('matrix', '=', comp.price_matrix.id), + ] + + if self.purchase_line: + domain += [ + ('origin', '=', self.purchase_line.from_location), + ('destination', '=', self.purchase_line.to_location), + ] + + lines = MatrixLine.search(domain) + if lines: + return Decimal(lines[0].price_value) + + return Decimal(0) + + def run_daily_mtm(): + Strategy = Pool().get('mtm.strategy') + Snapshot = Pool().get('mtm.snapshot') + + for strat in Strategy.search([('active', '=', True)]): + amount = strat.compute_mtm() + Snapshot.create([{ + 'strategy': strat.id, + 'valuation_date': strat.scenario.valuation_date, + 'amount': amount, + 'currency': strat.currency.id, + }]) + class Mtm(ModelSQL, ModelView): - "Mtm" + "MtM Component" __name__ = 'mtm.component' - fix_type = fields.Many2One('price.fixtype',"Fixation type") - ratio = fields.Numeric("%") - price_index = fields.Many2One('price.price',"Curve") - currency = fields.Function(fields.Many2One('currency.currency',"Curr."),'get_cur') + strategy = fields.Many2One( + 'mtm.strategy', "Strategy", + required=True, ondelete='CASCADE' + ) - def get_cur(self,name): + name = fields.Char("Component", required=True) + + component_type = fields.Selection([ + ('commodity', 'Commodity'), + ('freight', 'Freight'), + ('quality', 'Quality'), + ('fx', 'FX'), + ('storage', 'Storage'), + ('other', 'Other'), + ], "Type", required=True) + + fix_type = fields.Many2One('price.fixtype', "Fixation Type") + + price_source_type = fields.Selection([ + ('curve', 'Curve'), + ('matrix', 'Matrix'), + ('manual', 'Manual'), + ], "Price Source", required=True) + + price_index = fields.Many2One('price.price', "Price Curve") + price_matrix = fields.Many2One('price.matrix', "Price Matrix") + + ratio = fields.Numeric("Ratio / %", digits=(16, 6)) + + manual_price = fields.Numeric( + "Manual Price", + digits=(16, 6), + help="Price set manually if price_source_type is 'manual'" + ) + + currency = fields.Many2One('currency.currency', "Currency") + + def get_cur(self, name=None): if self.price_index: - PI = Pool().get('price.price') - pi = PI(self.price_index) - return pi.price_currency - + return self.price_index.price_currency + if self.price_matrix: + return self.price_matrix.currency + return None + + @fields.depends('price_index','price_matrix') + def on_change_with_currency(self): + return self.get_cur() + +class PriceMatrix(ModelSQL, ModelView): + "Price Matrix" + __name__ = 'price.matrix' + + name = fields.Char("Name", required=True) + + matrix_type = fields.Selection([ + ('freight', 'Freight'), + ('location', 'Location Spread'), + ('quality', 'Quality'), + ('storage', 'Storage'), + ('other', 'Other'), + ], "Matrix Type", required=True) + + unit = fields.Many2One('product.uom', "Unit") + currency = fields.Many2One('currency.currency', "Currency") + + calendar = fields.Many2One( + 'price.calendar', "Calendar" + ) + + valid_from = fields.Date("Valid From") + valid_to = fields.Date("Valid To") + + lines = fields.One2Many( + 'price.matrix.line', 'matrix', "Lines" + ) + +class PriceMatrixLine(ModelSQL, ModelView): + "Price Matrix Line" + __name__ = 'price.matrix.line' + + matrix = fields.Many2One( + 'price.matrix', "Matrix", + required=True, ondelete='CASCADE' + ) + + origin = fields.Many2One('stock.location', "Origin") + destination = fields.Many2One('stock.location', "Destination") + + product = fields.Many2One('product.product', "Product") + quality = fields.Many2One('product.quality', "Quality") + + price_value = fields.Numeric("Price", digits=(16, 6)) + +class MtmSnapshot(ModelSQL, ModelView): + "MtM Snapshot" + __name__ = 'mtm.snapshot' + + strategy = fields.Many2One( + 'mtm.strategy', "Strategy", + required=True, ondelete='CASCADE' + ) + + valuation_date = fields.Date("Valuation Date", required=True) + + amount = fields.Numeric("MtM Amount", digits=(16, 6)) + currency = fields.Many2One('currency.currency', "Currency") + + created_at = fields.DateTime("Created At") + class Component(ModelSQL, ModelView): "Component" __name__ = 'pricing.component' diff --git a/modules/purchase_trade/pricing.xml b/modules/purchase_trade/pricing.xml index 6fbb83d..890c7b2 100755 --- a/modules/purchase_trade/pricing.xml +++ b/modules/purchase_trade/pricing.xml @@ -3,6 +3,11 @@ this repository contains the full copyright notices and license terms. --> + + tradon-mtm + icons/tradon-mtm.svg + + sale.pricing.summary tree @@ -104,5 +109,84 @@ this repository contains the full copyright notices and license terms. --> form period_form + + + mtm.scenario + form + mtm_scenario_form + + + mtm.scenario + tree + mtm_scenario_tree + + + mtm.strategy + form + mtm_strategy_form + + + mtm.strategy + tree + mtm_strategy_tree + + + price.matrix + form + price_matrix_form + + + price.matrix + tree + price_matrix_tree + + + + + + Strategy + mtm.strategy + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index c3d046a..0054eb4 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -64,7 +64,7 @@ class DocTypeTemplate(ModelSQL): class Mtm(metaclass=PoolMeta): "Mtm" __name__ = 'mtm.component' - line = fields.Many2One('purchase.line') + line = fields.Many2One('purchase.line',"Line",ondelete='CASCADE') class Estimated(metaclass=PoolMeta): "Estimated date" @@ -422,7 +422,11 @@ class Line(metaclass=PoolMeta): purchase_line = fields.Many2One('purchase.line',"Lines") fees = fields.One2Many('fee.fee', 'line', 'Fees')#, filter=[('product.type', '=', 'service')]) derivatives = fields.One2Many('derivative.derivative','line',"Derivatives") - mtm = fields.One2Many('mtm.component','line',"Mtm") + # mtm = fields.One2Many('mtm.component','line',"Mtm") + mtm = fields.One2Many( + 'mtm.strategy', 'line', + "MtM Strategies" + ) tol_min = fields.Numeric("Tol - in %",states={ 'readonly': (Eval('inherit_tol')), }) diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index b616122..6ab4ad4 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -55,7 +55,7 @@ class OpenPosition(metaclass=PoolMeta): class Mtm(metaclass=PoolMeta): "Mtm" __name__ = 'mtm.component' - sale_line = fields.Many2One('sale.line',"Line") + sale_line = fields.Many2One('sale.line',"Line",ondelete='CASCADE') class Component(metaclass=PoolMeta): "Component" @@ -363,7 +363,11 @@ class SaleLine(metaclass=PoolMeta): from_del = fields.Date("From") to_del = fields.Date("To") price_components = fields.One2Many('pricing.component','sale_line',"Components") - mtm = fields.One2Many('mtm.component','sale_line',"Mtm") + # mtm = fields.One2Many('mtm.component','sale_line',"Mtm") + mtm = fields.One2Many( + 'mtm.strategy', 'sale_line', + "MtM Strategies" + ) derivatives = fields.One2Many('derivative.derivative','sale_line',"Derivatives") price_pricing = fields.One2Many('pricing.pricing','sale_line',"Pricing") price_summary = fields.One2Many('sale.pricing.summary','sale_line',"Summary") diff --git a/modules/purchase_trade/view/mtm_scenario_tree.xml b/modules/purchase_trade/view/mtm_scenario_tree.xml new file mode 100644 index 0000000..4a3bf12 --- /dev/null +++ b/modules/purchase_trade/view/mtm_scenario_tree.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/modules/purchase_trade/view/mtm_strategy_form.xml b/modules/purchase_trade/view/mtm_strategy_form.xml new file mode 100644 index 0000000..061d23a --- /dev/null +++ b/modules/purchase_trade/view/mtm_strategy_form.xml @@ -0,0 +1,10 @@ +
+