from functools import wraps from trytond.model import ModelSingleton, ModelSQL, ModelView, fields from trytond.report import Report from trytond.pool import Pool, PoolMeta from trytond.pyson import Bool, Eval, Id, If, PYSONEncoder from trytond.model import (ModelSQL, ModelView) from trytond.tools import (cursor_dict, is_full_text, lstrip_wildcard) from trytond.transaction import Transaction, inactive_records from decimal import getcontext, Decimal, ROUND_HALF_UP from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr from sql.conditionals import Case from sql.functions import CurrentTimestamp, DateTrunc, Abs from sql import Column, Literal, Union, Select from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction from itertools import chain, groupby from operator import itemgetter import datetime import logging import json import jwt from collections import defaultdict from trytond.exceptions import UserWarning, UserError logger = logging.getLogger(__name__) class OpenPosition(ModelSQL): "Open position" __name__ = 'open.position' product = fields.Many2One('product.product', "Product", required=True) purchase = fields.Many2One('purchase.purchase',"Purchase") line = fields.Many2One('purchase.line',"Line") supplier = fields.Many2One('party.party', "Supplier", help="Optional counterparty aggregation") currency = fields.Many2One('currency.currency', "Currency", required=True) uom = fields.Many2One('product.uom', "Unit of Measure") period_start = fields.Date("Period start") period_end = fields.Date("Period end") type = fields.Selection([ ('priced', 'Priced'), ('hedge', 'Hedge'), ('physic', 'Physic'), ('shipped', 'Shipped'), ('open', 'Open') ], "Type") physical_qty = fields.Numeric("Priced Quantity", digits=(16, 6)) hedged_qty = fields.Numeric("Hedged Quantity", digits=(16, 6)) net_exposure = fields.Numeric("Open Quantity", digits=(16, 6)) price = fields.Numeric("Price", digits=(16, 6)) amount = fields.Numeric("Amount", digits=(16, 6)) curve = fields.Many2One('price.price') mtm_price = fields.Numeric("Mtm price", digits=(16, 6)) mtm = fields.Numeric("Mtm amount", digits=(16, 2),help="Unrealised MTM for open quantities (in currency)") sensitivity = fields.Numeric("Sensitivity (per unit)", digits=(16, 6),help="MTM change per 1 unit change in price") @classmethod def create_from_sale_line(cls, line): Date = Pool().get('ir.date') OpenPosition = Pool().get('open.position') op = OpenPosition.search([('sale_line','=',line.id)]) if op: OpenPosition.delete(op) vals = { 'product': line.product.id, 'sale': line.sale.id, 'sale_line': line.id, 'client': line.sale.party.id, 'currency': line.sale.currency.id, 'uom': line.unit.id, 'period_start': line.from_del, 'period_end': line.to_del } ##OPEN PART### vals['type'] = 'open' vals['physical_qty'] = round(line.quantity_theorical - Decimal(line.quantity),5) if vals['physical_qty'] == Decimal(0) and line.lots and len(line.lots)==1: vals['physical_qty'] = Decimal(line.quantity) vals['physical_qty'] = Decimal(vals['physical_qty'])*Decimal(-1) vals['hedged_qty'] = Decimal(0) vals['net_exposure'] = vals['physical_qty'] vals['price'] = line.unit_price vals['amount'] = round(vals['price'] * vals['physical_qty'],2) if line.mtm: for mt in line.mtm: vals['curve'] = mt.price_index.id vals['mtm_price'] = round(Decimal(mt.price_index.get_price(Date.today(),line.unit,line.currency,True)),4) vals['mtm'] = round(vals['mtm_price'] * vals['physical_qty'],2) OpenPosition.create([vals]) else: OpenPosition.create([vals]) @classmethod def create_from_purchase_line(cls, line): OpenPosition = Pool().get('open.position') Date = Pool().get('ir.date') op = OpenPosition.search([('line','=',line.id)]) if op: OpenPosition.delete(op) vals = { 'product': line.product.id, 'purchase': line.purchase.id, 'line': line.id, 'supplier': line.purchase.party.id, 'currency': line.purchase.currency.id, 'uom': line.unit.id, 'period_start': line.from_del, 'period_end': line.to_del } ##OPEN PART### vals['type'] = 'open' vals['physical_qty'] = round(line.quantity_theorical - Decimal(line.quantity),5) if vals['physical_qty'] == Decimal(0) and line.lots and len(line.lots)==1: vals['physical_qty'] = Decimal(line.quantity) vals['hedged_qty'] = Decimal(0) vals['net_exposure'] = vals['physical_qty'] vals['price'] = line.unit_price vals['amount'] = round(vals['price'] * vals['physical_qty'],2) if line.mtm: for mt in line.mtm: vals['curve'] = mt.price_index.id vals['mtm_price'] = round(Decimal(mt.price_index.get_price(Date.today(),line.unit,line.currency,True)),4) vals['mtm'] = round(vals['mtm_price'] * vals['physical_qty'],2) OpenPosition.create([vals]) else: OpenPosition.create([vals]) ##PHYSIC PART### for lot in line.lots: if not lot.invoice_line and not lot.invoice_line_prov and lot.lot_type == 'physic': if lot.GetShipment('in'): vals['type'] = 'shipped' else: vals['type'] = 'physic' vals['physical_qty'] = lot.get_current_quantity_converted() vals['hedged_qty'] = Decimal(0) vals['net_exposure'] = vals['physical_qty'] vals['price'] = lot.get_lot_price() vals['amount'] = round(vals['price'] * vals['physical_qty'],2) if line.mtm: for mt in line.mtm: vals['curve'] = mt.price_index.id vals['mtm_price'] = round(Decimal(mt.price_index.get_price(Date.today(),line.unit,line.currency,True)),4) vals['mtm'] = round(vals['mtm_price'] * vals['physical_qty'],2) OpenPosition.create([vals]) else: OpenPosition.create([vals]) ##DERIVATIVE PART### if line.derivatives: for d in line.derivatives: vals['type'] = 'hedge' vals['physical_qty'] = Decimal(0) vals['hedged_qty'] = -d.quantity vals['net_exposure'] = d.quantity vals['price'] = d.price vals['amount'] = round(vals['price'] * d.quantity,2) vals['curve'] = d.price_index.id vals['mtm_price'] = round(Decimal(d.price_index.get_price(Date.today(),line.unit,line.currency,True)),4) vals['mtm'] = round(vals['mtm_price'] * d.quantity,2) OpenPosition.create([vals]) class OpenPositionReport( ModelSQL, ModelView): "Open position report" __name__ = 'open.position.report' product = fields.Many2One('product.product', "Product", required=True) purchase = fields.Many2One('purchase.purchase',"Purchase") line = fields.Many2One('purchase.line',"Line") sale = fields.Many2One('sale.sale',"Sale") sale_line = fields.Many2One('sale.line',"Sale Line") supplier = fields.Many2One('party.party', "Supplier") client = fields.Many2One('party.party', "Client") currency = fields.Many2One('currency.currency', "Currency", required=True) uom = fields.Many2One('product.uom', "Unit of Measure") period_start = fields.Date("Period start") period_end = fields.Date("Period end") type = fields.Selection([ ('priced', 'Priced'), ('hedge', 'Hedge'), ('physic', 'Physic'), ('shipped', 'Shipped'), ('open', 'Open') ], "Type") physical_qty = fields.Numeric("Priced Quantity", digits=(16, 5)) hedged_qty = fields.Numeric("Hedged Quantity", digits=(16, 5)) net_exposure = fields.Numeric("Open Quantity", digits=(16, 5)) price = fields.Numeric("Price", digits=(16, 4)) amount = fields.Numeric("Amount", digits=(16, 2)) curve = fields.Many2One('price.price') mtm_price = fields.Numeric("Mtm price", digits=(16, 4)) mtm = fields.Numeric("Mtm amount", digits=(16, 2),help="Unrealised MTM for open quantities (in currency)") pnl = fields.Numeric("Pnl", digits=(16, 2)) sensitivity = fields.Numeric("Sensitivity (per unit)", digits=(16, 1),help="MTM change per 1 unit change in price") @classmethod def table_query(cls): OpenPosition = Pool().get('open.position') op = OpenPosition.__table__() context = Transaction().context supplier = context.get('supplier') purchase = context.get('purchase') client = context.get('client') sale = context.get('sale') product = context.get('product') asof = context.get('asof') todate = context.get('todate') state = context.get('state') wh = Literal(True) if supplier: wh &= (op.supplier == supplier) if client: wh &= (op.client == client) if product: wh &= (op.product == product) if purchase: wh &= (op.purchase == purchase) if sale: wh &= (op.sale == sale) # if asof and todate: # wh &= (pu.purchase_date >= asof) & (pu.purchase_date <= todate) query = ( op .select( Literal(0).as_('create_uid'), CurrentTimestamp().as_('create_date'), Literal(0).as_('write_uid'), Literal(0).as_('write_date'), op.id.as_('id'), op.product.as_('product'), op.supplier.as_('supplier'), op.client.as_('client'), op.purchase.as_('purchase'), op.sale.as_('sale'), op.line.as_('line'), op.sale_line.as_('sale_line'), op.type.as_('type'), op.currency.as_('currency'), op.uom.as_('uom'), op.period_start.as_('period_start'), op.period_end.as_('period_end'), op.physical_qty.as_('physical_qty'), op.hedged_qty.as_('hedged_qty'), op.net_exposure.as_('net_exposure'), op.price.as_('price'), op.amount.as_('amount'), op.curve.as_('curve'), op.mtm_price.as_('mtm_price'), op.mtm.as_('mtm'), (op.amount-op.mtm).as_('pnl'), op.sensitivity.as_('sensitivity'), where=wh ) ) return query class OpenPositionContext(ModelView): "Open Position Context" __name__ = 'open.position.context' asof = fields.Date("As of") todate = fields.Date("To") supplier = fields.Many2One('party.party',"Supplier") client = fields.Many2One('party.party',"Client") product = fields.Many2One('product.product',"Product") purchase = fields.Many2One('purchase.purchase', "Purchase") sale = fields.Many2One('sale.sale',"Sale") state = fields.Selection([ ('all', 'All'), ('open', 'Open'), ('fixed', 'Fixed'), ('hedged', 'Hedged') ], 'State') @classmethod def default_asof(cls): pool = Pool() Date = pool.get('ir.date') return Date.today().replace(day=1,month=1,year=1999) @classmethod def default_todate(cls): pool = Pool() Date = pool.get('ir.date') return Date.today() @classmethod def default_state(cls): return 'all'