diff --git a/modules/purchase/purchase.py b/modules/purchase/purchase.py index 9215127..c197555 100755 --- a/modules/purchase/purchase.py +++ b/modules/purchase/purchase.py @@ -1685,7 +1685,7 @@ class Line(sequence_ordered(), ModelSQL, ModelView): @fields.depends( 'type', 'quantity', 'unit_price', - 'purchase', '_parent_purchase.currency') + 'purchase', '_parent_purchase.currency','premium') def on_change_with_amount(self): if (self.type == 'line' and self.quantity is not None diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py index 118eab8..3e449b1 100755 --- a/modules/purchase_trade/__init__.py +++ b/modules/purchase_trade/__init__.py @@ -3,7 +3,32 @@ from trytond.pool import Pool -from . import purchase,sale,global_reporting,stock,derivative,lot,pricing,workflow,lc,dashboard,fee,payment_term,purchase_prepayment,cron,party,forex,outgoing,incoming,optional,association_tables, document_tracking, open_position, credit_risk +from . import ( + purchase, + sale, + global_reporting, + stock, + derivative, + lot, + pricing, + workflow, + lc, + dashboard, + fee, + payment_term, + purchase_prepayment, + cron, + party, + forex, + outgoing, + incoming, + optional, + association_tables, + document_tracking, + open_position, + credit_risk, + valuation, +) def register(): Pool.register( @@ -69,8 +94,11 @@ def register(): fee.Fee, fee.FeeLots, purchase.FeeLots, - fee.Valuation, - fee.ValuationDyn, + valuation.Valuation, + valuation.ValuationLine, + valuation.ValuationDyn, + valuation.ValuationReport, + valuation.ValuationReportContext, derivative.Derivative, derivative.DerivativeMatch, derivative.MatchWizardStart, @@ -151,6 +179,8 @@ def register(): sale.SaleCreatePurchaseInput, sale.Derivative, sale.Valuation, + sale.ValuationLine, + sale.ValuationReport, sale.Fee, sale.Lot, sale.FeeLots, diff --git a/modules/purchase_trade/dashboard.py b/modules/purchase_trade/dashboard.py index 4f17f73..6b5cb63 100755 --- a/modules/purchase_trade/dashboard.py +++ b/modules/purchase_trade/dashboard.py @@ -193,7 +193,7 @@ class Dashboard(ModelSQL, ModelView): self.chatbot = 'chatbot:' + json.dumps(dial, ensure_ascii=False) logger.info("EXITONCHANGE",self.chatbot) - def get_last_two_fx_rates(self, from_code='USD', to_code='EUR'): + def get_last_five_fx_rates(self, from_code='USD', to_code='EUR'): """ Retourne (dernier_taux, avant_dernier_taux) pour le couple de devises. """ @@ -208,29 +208,42 @@ class Dashboard(ModelSQL, ModelView): rates = CurrencyRate.search( [('currency', '=', to_currency.id)], order=[('date', 'DESC')], - limit=2, + limit=5, ) if not rates: - return None, None + return None, None, None, None, None # Calcul du taux EUR/USD # Si la devise principale de la société est EUR, et que le taux stocké est # "1 USD = X EUR", on veut l'inverse pour avoir EUR/USD - last_rate = rates[0].rate - prev_rate = rates[1].rate if len(rates) > 1 else None + f1 = rates[0].rate + f2 = rates[1].rate if len(rates) > 1 else None + f3 = rates[2].rate if len(rates) > 2 else None + f4 = rates[3].rate if len(rates) > 3 else None + f5 = rates[4].rate if len(rates) > 4 else None + d1 = rates[0].date + d2 = rates[1].date if len(rates) > 1 else None + d3 = rates[2].date if len(rates) > 2 else None + d4 = rates[3].date if len(rates) > 3 else None + d5 = rates[4].date if len(rates) > 4 else None # if from_currency != to_currency: # last_rate = 1 / last_rate if last_rate else None # prev_rate = 1 / prev_rate if prev_rate else None - if last_rate and prev_rate: - return round(1/last_rate,6), round(1/prev_rate,6) + return round(1/f1,6), round(1/f2,6) if f2 else None, round(1/f3,6) if f3 else None, round(1/f4,6) if f4 else None, round(1/f5,6) if f5 else None, d1, d2, d3, d4, d5 def get_tremor(self,name): - Pnl = Pool().get('valuation.valuation') - pnls = Pnl.search(['id','>',0]) - pnl_amount = "{:,.0f}".format(round(sum([e.amount for e in pnls]),0)) + Configuration = Pool().get('gr.configuration') + config = Configuration.search(['id','>',0])[0] + f1,f2,f3,f4,f5,d1,d2,d3,d4,d5 = self.get_last_five_fx_rates() + Valuation = Pool().get('valuation.valuation') + total_t, total_t1, variation = Valuation.get_totals() + pnl_amount = "{:,.0f}".format(round(total_t,0)) + pnl_variation = 0 + if total_t1: + pnl_variation = "{:,.2f}".format(round((total_t/total_t1 - 1)*100,0)) Open = Pool().get('open.position') opens = Open.search(['id','>',0]) exposure = "{:,.0f}".format(round(sum([e.net_exposure for e in opens]),0)) @@ -261,35 +274,66 @@ class Dashboard(ModelSQL, ModelView): Shipment = Pool().get('stock.shipment.in') draft = Shipment.search(['state','=','draft']) shipment_d = len(draft) - val = Purchase.search(['state','=','started']) + val = Shipment.search(['state','=','started']) shipment_s = len(val) - conf = Purchase.search(['state','=','received']) + conf = Shipment.search(['state','=','received']) shipment_r = len(conf) Lot = Pool().get('lot.lot') - lots = Lot.search(['sale_line','!=',None]) + lots = Lot.search([('sale_line','!=',None),('line','!=',None),('lot_type','=','physic')]) lot_m = len(lots) - val = Lot.search(['sale_line','=',None]) + val = Lot.search([('sale_line','=',None),('line','!=',None),('lot_type','=','physic')]) lot_a = len(val) - conf = Lot.search(['lot_type','=','physic']) - lot_al = len(conf) Invoice = Pool().get('account.invoice') invs = Invoice.search(['type','=','in']) inv_p = len(invs) + invs = Invoice.search([('type','=','in'),('state','=','paid')]) + inv_p_p = len(invs) + invs = Invoice.search([('type','=','in'),('state','!=','paid')]) + inv_p_np = len(invs) invs = Invoice.search(['type','=','out']) inv_s = len(invs) + invs = Invoice.search([('type','=','out'),('state','=','paid')]) + inv_s_p = len(invs) + invs = Invoice.search([('type','=','out'),('state','!=','paid')]) + inv_s_np = len(invs) AccountMove = Pool().get('account.move') - accs = AccountMove.search(['id','>',0]) - move_cash = len(accs) + accs = AccountMove.search([('journal','=',3),('state','!=','posted')]) + pay_val = len(accs) + accs = AccountMove.search([('journal','=',3),('state','=','posted')]) + pay_posted = len(accs) return ( - "https://srv413259.hstgr.cloud/dashboard/index.html?pnl_amount=" + config.dashboard + + "/dashboard/index.html?pnl_amount=" + str(pnl_amount) + + "&pnl_variation=" + + str(pnl_variation) + "&exposure=" + str(exposure) + "&topay=" + str(topay) + "&toreceive=" + str(toreceive) + + "&eurusd=" + + str(f1) + + "&eurusd=" + + str(f2) + + "&eurusd=" + + str(f3) + + "&eurusd=" + + str(f4) + + "&eurusd=" + + str(f5) + + "&eurusd_date=" + + str(d1) + + "&eurusd_date=" + + str(d2) + + "&eurusd_date=" + + str(d3) + + "&eurusd_date=" + + str(d4) + + "&eurusd_date=" + + str(d5) + "&draft_p=" + str(draft_p) + "&val_p=" @@ -312,14 +356,22 @@ class Dashboard(ModelSQL, ModelView): + str(lot_m) + "&lot_a=" + str(lot_a) - + "&lot_al=" - + str(lot_al) + "&inv_p=" + str(inv_p) + + "&inv_p_p=" + + str(inv_p_p) + + "&inv_p_np=" + + str(inv_p_np) + "&inv_s=" + str(inv_s) - + "&move_cash=" - + str(move_cash) + + "&inv_s_p=" + + str(inv_s_p) + + "&inv_s_np=" + + str(inv_s_np) + + "&pay_val=" + + str(pay_val) + + "&pay_posted=" + + str(pay_posted) ) @@ -327,7 +379,7 @@ class Dashboard(ModelSQL, ModelView): News = Pool().get('news.news') Date = Pool().get('ir.date') news_list = News.search([('active', '=', True)], limit=5, order=[('publish_date', 'DESC')]) - last_rate,prev_rate = self.get_last_two_fx_rates() + last_rate,prev_rate, = self.get_last_five_fx_rates() if last_rate and prev_rate: variation = ((last_rate - prev_rate) / prev_rate) * 100 if prev_rate else 0 direction = "📈" if variation > 0 else "📉" diff --git a/modules/purchase_trade/fee.py b/modules/purchase_trade/fee.py index 02c74f6..62fd50e 100755 --- a/modules/purchase_trade/fee.py +++ b/modules/purchase_trade/fee.py @@ -21,117 +21,6 @@ from trytond.exceptions import UserWarning, UserError logger = logging.getLogger(__name__) -VALTYPE = [ - ('priced', 'Price'), - ('pur. priced', 'Pur. price'), - ('pur. efp', 'Pur. efp'), - ('sale priced', 'Sale price'), - ('sale efp', 'Sale efp'), - ('line fee', 'Line fee'), - ('pur. fee', 'Pur. fee'), - ('sale fee', 'Sale fee'), - ('shipment fee', 'Shipment fee'), - ('market', 'Market'), - ('derivative', 'Derivative'), -] - -class Valuation(ModelSQL,ModelView): - "Valuation" - __name__ = 'valuation.valuation' - - purchase = fields.Many2One('purchase.purchase',"Purchase") - line = fields.Many2One('purchase.line',"Purch. Line") - date = fields.Date("Date") - type = fields.Selection(VALTYPE, "Type") - reference = fields.Char("Reference") - counterparty = fields.Many2One('party.party',"Counterparty") - product = fields.Many2One('product.product',"Product") - state = fields.Char("State") - price = fields.Numeric("Price",digits='unit') - currency = fields.Many2One('currency.currency',"Cur") - quantity = fields.Numeric("Quantity",digits='unit') - unit = fields.Many2One('product.uom',"Unit") - amount = fields.Numeric("Amount",digits='unit') - mtm = fields.Numeric("Mtm",digits='unit') - lot = fields.Many2One('lot.lot',"Lot") - -class ValuationDyn(ModelSQL,ModelView): - "Valuation" - __name__ = 'valuation.valuation.dyn' - - r_purchase = fields.Many2One('purchase.purchase',"Purchase") - r_line = fields.Many2One('purchase.line',"Line") - r_date = fields.Date("Date") - r_type = fields.Selection(VALTYPE, "Type") - r_reference = fields.Char("Reference") - r_counterparty = fields.Many2One('party.party',"Counterparty") - r_product = fields.Many2One('product.product',"Product") - r_state = fields.Char("State") - r_price = fields.Numeric("Price",digits='r_unit') - r_currency = fields.Many2One('currency.currency',"Cur") - r_quantity = fields.Numeric("Quantity",digits='r_unit') - r_unit = fields.Many2One('product.uom',"Unit") - r_amount = fields.Numeric("Amount",digits='r_unit') - r_mtm = fields.Numeric("Mtm",digits='r_unit') - r_lot = fields.Many2One('lot.lot',"Lot") - - @classmethod - def table_query(cls): - Valuation = Pool().get('valuation.valuation') - val = Valuation.__table__() - context = Transaction().context - group_pnl = context.get('group_pnl') - wh = (val.id > 0) - # query = val.select( - # Literal(0).as_('create_uid'), - # CurrentTimestamp().as_('create_date'), - # Literal(None).as_('write_uid'), - # Literal(None).as_('write_date'), - # val.id.as_('id'), - # val.purchase.as_('r_purchase'), - # val.line.as_('r_line'), - # val.date.as_('r_date'), - # val.type.as_('r_type'), - # val.reference.as_('r_reference'), - # val.counterparty.as_('r_counterparty'), - # val.product.as_('r_product'), - # val.state.as_('r_state'), - # val.price.as_('r_price'), - # val.currency.as_('r_currency'), - # val.quantity.as_('r_quantity'), - # val.unit.as_('r_unit'), - # val.amount.as_('r_amount'), - # val.mtm.as_('r_mtm'), - # val.lot.as_('r_lot'), - # where=wh) - - #if group_pnl==True: - query = val.select( - Literal(0).as_('create_uid'), - CurrentTimestamp().as_('create_date'), - Literal(None).as_('write_uid'), - Literal(None).as_('write_date'), - Max(val.id).as_('id'), - Max(val.purchase).as_('r_purchase'), - Max(val.line).as_('r_line'), - Max(val.date).as_('r_date'), - val.type.as_('r_type'), - Max(val.reference).as_('r_reference'), - val.counterparty.as_('r_counterparty'), - Max(val.product).as_('r_product'), - val.state.as_('r_state'), - Avg(val.price).as_('r_price'), - 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.mtm).as_('r_mtm'), - Max(val.lot).as_('r_lot'), - where=wh, - group_by=[val.type,val.counterparty,val.state]) - - return query - def filter_state(state): def filter(func): @wraps(func) diff --git a/modules/purchase_trade/fee.xml b/modules/purchase_trade/fee.xml index dca08ce..424b39f 100755 --- a/modules/purchase_trade/fee.xml +++ b/modules/purchase_trade/fee.xml @@ -19,26 +19,6 @@ this repository contains the full copyright notices and license terms. --> fee_tree_sequence - - valuation.valuation - tree - valuation_tree_sequence3 - - - valuation.valuation - graph - valuation_graph - - - valuation.valuation - graph - valuation_graph2 - - - valuation.valuation.dyn - tree - valuation_tree_sequence4 - fee.fee tree diff --git a/modules/purchase_trade/global_reporting.py b/modules/purchase_trade/global_reporting.py index 6d0da8f..45aa6eb 100644 --- a/modules/purchase_trade/global_reporting.py +++ b/modules/purchase_trade/global_reporting.py @@ -12,4 +12,5 @@ class GRConfiguration(ModelSingleton, ModelSQL, ModelView): __name__ = 'gr.configuration' bi = fields.Char("BI connexion") + dashboard = fields.Char("Dashboard connexion") dark = fields.Boolean("Dark mode") \ No newline at end of file diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py index f135c88..3d5a2c1 100755 --- a/modules/purchase_trade/lot.py +++ b/modules/purchase_trade/lot.py @@ -1168,6 +1168,7 @@ class LotQt( @classmethod def validate(cls, lotqts): super(LotQt, cls).validate(lotqts) + Date = Pool().get('ir.date') #Update Move for lqt in lotqts: cls.updateMove(lqt.lot_move) @@ -1177,15 +1178,15 @@ class LotQt( if lqt.lot_p and lqt.lot_quantity > 0: pl = lqt.lot_p.line logger.info("VALIDATE_LQT_PL:%s",pl) - Pnl = Pool().get('valuation.valuation') - pnl = Pnl.search([('line','=',pl.id)]) - if pnl: - Pnl.delete(pnl) - pnl_lines = [] - pnl_lines.extend(pl.get_pnl_fee_lines()) - pnl_lines.extend(pl.get_pnl_price_lines()) - pnl_lines.extend(pl.get_pnl_der_lines()) - Pnl.save(pnl_lines) + # Pnl = Pool().get('valuation.valuation') + # pnl = Pnl.search([('line','=',pl.id),('date','=',Date.today())]) + # if pnl: + # Pnl.delete(pnl) + # pnl_lines = [] + # pnl_lines.extend(pl.get_pnl_fee_lines()) + # pnl_lines.extend(pl.get_pnl_price_lines()) + # pnl_lines.extend(pl.get_pnl_der_lines()) + # Pnl.save(pnl_lines) #Open position update if pl.quantity_theorical: diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 4c9f0a0..643e31d 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -252,21 +252,12 @@ class Purchase(metaclass=PoolMeta): broker = fields.Many2One('party.party',"Broker",domain=[('categories.parent', 'child_of', [4])]) tol_min = fields.Numeric("Tol - in %") tol_max = fields.Numeric("Tol + in %") - # certification = fields.Selection([ - # (None, ''), - # ('bci', 'BCI'), - # ],"Certification") certif = fields.Many2One('purchase.certification',"Certification") wb = fields.Many2One('purchase.weight.basis',"Weight basis") association = fields.Many2One('purchase.association',"Association") crop = fields.Many2One('purchase.crop',"Crop") - # weight_basis = fields.Selection([ - # (None, ''), - # ('ncsw', 'NCSW'), - # ('nlw', 'NLW'), - # ], 'Weight basis') pnl = fields.One2Many('valuation.valuation.dyn', 'r_purchase', 'Pnl',states={'invisible': ~Eval('group_pnl'),}) - pnl_ = fields.One2Many('valuation.valuation', 'purchase', 'Pnl',states={'invisible': Eval('group_pnl'),}) + pnl_ = fields.One2Many('valuation.valuation.line', 'purchase', 'Pnl',states={'invisible': Eval('group_pnl'),}) derivatives = fields.One2Many('derivative.derivative', 'purchase', 'Derivative') plans = fields.One2Many('workflow.plan','purchase',"Execution plans") forex = fields.One2Many('forex.cover.physical.contract','contract',"Forex",readonly=True) @@ -380,19 +371,11 @@ class Purchase(metaclass=PoolMeta): Decimal(str(line.quantity)) .quantize(Decimal("0.00001")) ) - Line.save([line]) #compute pnl Pnl = Pool().get('valuation.valuation') - pnl = Pnl.search([('line','=',line.id)]) - if pnl: - Pnl.delete(pnl) - pnl_lines = [] - pnl_lines.extend(line.get_pnl_fee_lines()) - pnl_lines.extend(line.get_pnl_price_lines()) - pnl_lines.extend(line.get_pnl_der_lines()) - Pnl.save(pnl_lines) + Pnl.generate(line) if line.quantity_theorical: OpenPosition = Pool().get('open.position') @@ -655,358 +638,6 @@ class Line(metaclass=PoolMeta): f.purchase = line.purchase Fee.save([f]) - def get_pnl_der_lines(self): - der_lines = [] - if self.derivatives: - Pnl = Pool().get('valuation.valuation') - Date = Pool().get('ir.date') - for d in self.derivatives: - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.line = self.id - pnl.type = 'derivative' - pnl.date = Date.today() - pnl.reference = d.price_index.price_index - pnl.price = round(Decimal(d.price_index.get_price_per_qt(d.price,self.unit,self.purchase.currency)),4) - pnl.counterparty = d.party - pnl.product = d.product - pnl.state = 'fixed' - pnl.amount = round(pnl.price * (d.quantity) * Decimal(-1),4) - mtm = round(Decimal(d.price_index.get_price(Date.today(),self.unit,self.purchase.currency,True)) * d.quantity * Decimal(-1),4) - pnl.mtm = pnl.amount - mtm - pnl.quantity = round(d.quantity,5) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - der_lines.append(pnl) - return der_lines - - def get_pnl_price_lines(self): - price_lines = [] - Pnl = Pool().get('valuation.valuation') - LotQt = Pool().get('lot.qt') - Date = Pool().get('ir.date') - for lot in self.lots: - logger.info("FROM_VALUATION_TYPE:%s",self.price_type) - if self.price_type == 'basis' and self.price_summary: - for pc in self.price_summary: - #pnl management - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.line = self.id - pnl.type = 'pur. priced' - pnl.date = Date.today() - pnl.lot = lot.id - if lot.sale_line: - pnl.sale = lot.sale_line.sale.id - pnl.reference = pc.get_name() + ' / ' + str(pc.ratio) + '%' - pnl.price = round(pc.price,4) - pnl.counterparty = self.purchase.party - pnl.product = self.product - if pc.unfixed_qt == 0: - pnl.state = 'fixed' - elif pc.fixed_qt == 0: - pnl.state = 'unfixed' - else: - pnl.state = 'part. fixed' + ' ' + str(round(pc.fixed_qt / Decimal(self.quantity_theorical) * 100,0)) + '%' - if pc.price and pc.ratio: - #pnl.amount = round(pc.price * (pc.unfixed_qt + pc.fixed_qt) * Decimal(-1) * pc.ratio / 100,2) - pnl.amount = round(pc.price * lot.get_current_quantity_converted() * Decimal(-1) * pc.ratio / 100,4) - last_price = pc.get_last_price() - mtm = Decimal(0) - if last_price: - mtm = round(Decimal(last_price) * lot.get_current_quantity_converted() * Decimal(-1),4) - pnl.mtm = round(pnl.amount - (mtm * pc.ratio / 100),4) - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - elif self.price_type == 'priced': - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.line = self.id - pnl.type = 'pur. priced' - pnl.date = Date.today() - pnl.lot = lot.id - if lot.sale_line: - pnl.sale = lot.sale_line.sale.id - if lot.lot_type == 'physic': - pnl.reference = 'Purchase/Physic' - else: - pnl.reference = 'Purchase/Open' - if lot.lot_price: - pnl.price = round(lot.lot_price,4) - pnl.counterparty = self.purchase.party - pnl.product = self.product - pnl.state = 'fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity * Decimal(-1),4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - elif self.price_type == 'efp': - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.line = self.id - pnl.type = 'pur. efp' - pnl.date = Date.today() - pnl.lot = lot.id - if lot.sale_line: - pnl.sale = lot.sale_line.sale.id - if lot.lot_type == 'physic': - pnl.reference = 'Purchase/Physic' - else: - pnl.reference = 'Purchase/Open' - if lot.lot_price: - pnl.price = round(lot.lot_price,4) - pnl.counterparty = self.purchase.party - pnl.product = self.product - pnl.state = 'not fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity * Decimal(-1),4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - if lot.sale_line: - sl = lot - if sl.sale_line.price_type == 'basis' and sl.sale_line.price_summary: - for pc in sl.sale_line.price_summary: - #pnl management - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.type = 'sale priced' - pnl.date = Date.today() - pnl.lot = lot.id - pnl.reference = pc.get_name() + ' / ' + str(pc.ratio) + '%' - pnl.price = round(pc.price,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = sl.sale_line.product - if pc.unfixed_qt == 0: - pnl.state = 'fixed' - elif pc.fixed_qt == 0: - pnl.state = 'unfixed' - else: - pnl.state = 'part. fixed' + ' ' + str(round(pc.fixed_qt / Decimal(sl.sale_line.quantity_theorical) * 100,0)) + '%' - if pc.price and pc.ratio: - #pnl.amount = round(pc.price * (pc.unfixed_qt + pc.fixed_qt) * Decimal(-1) * pc.ratio / 100,2) - pnl.amount = round(pc.price * sl.get_current_quantity_converted() * pc.ratio / 100,4) - last_price = pc.get_last_price() - mtm = Decimal(0) - if last_price: - mtm = round(Decimal(last_price) * sl.get_current_quantity_converted(),4) - pnl.mtm = round(pnl.amount - (mtm * pc.ratio / 100),4) - pnl.quantity = round(sl.get_current_quantity_converted(),5) - pnl.unit = sl.sale_line.unit - pnl.currency = sl.sale_line.sale.currency - price_lines.append(pnl) - elif sl.sale_line.price_type == 'priced': - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.type = 'sale priced' - pnl.date = Date.today() - pnl.lot = lot.id - if lot.lot_type == 'physic': - pnl.reference = 'Sale/Physic' - else: - pnl.reference = 'Sale/Open' - pnl.price = round(lot.lot_price_sale,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = self.product - pnl.state = 'fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity,4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - elif sl.sale_line.price_type == 'efp': - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.type = 'sale efp' - pnl.date = Date.today() - pnl.lot = lot.id - if lot.lot_type == 'physic': - pnl.reference = 'Sale/Physic' - else: - pnl.reference = 'Sale/Open' - pnl.price = round(lot.lot_price_sale,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = self.product - pnl.state = 'not fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity,4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - lqts = LotQt.search([('lot_p','=',lot.id),('lot_s','>',0),('lot_quantity','>',0)]) - logger.info("FROM_VALUATION:%s",lqts) - logger.info("FROM_VALUATION2:%s",lot.sale_line) - if lqts and not lot.sale_line: - for lqt in lqts: - sl = lqt.lot_s - if sl.sale_line.price_type == 'basis' and sl.sale_line.price_summary: - for pc in sl.sale_line.price_summary: - #pnl management - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.lot = sl.id - pnl.type = 'sale priced' - pnl.date = Date.today() - pnl.reference = pc.get_name() + ' / ' + str(pc.ratio) + '%' - pnl.price = round(pc.price,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = sl.sale_line.product - if pc.unfixed_qt == 0: - pnl.state = 'fixed' - elif pc.fixed_qt == 0: - pnl.state = 'unfixed' - else: - pnl.state = 'part. fixed' + ' ' + str(round(pc.fixed_qt / Decimal(sl.sale_line.quantity_theorical) * 100,0)) + '%' - if pc.price and pc.ratio: - #pnl.amount = round(pc.price * (pc.unfixed_qt + pc.fixed_qt) * Decimal(-1) * pc.ratio / 100,2) - pnl.amount = round(pc.price * sl.get_current_quantity_converted() * pc.ratio / 100,4) - last_price = pc.get_last_price() - mtm = Decimal(0) - if last_price: - mtm = round(Decimal(last_price) * sl.get_current_quantity_converted(),4) - pnl.mtm = round(pnl.amount - (mtm * pc.ratio / 100),4) - pnl.quantity = round(sl.get_current_quantity_converted(),5) - pnl.unit = sl.sale_line.unit - pnl.currency = sl.sale_line.sale.currency - price_lines.append(pnl) - elif sl.sale_line.price_type == 'priced': - logger.info("FROM_VALUATION3:%s",sl) - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.type = 'sale priced' - pnl.date = Date.today() - pnl.lot = sl.id - if sl.lot_type == 'physic': - pnl.reference = 'Sale/Physic' - else: - pnl.reference = 'Sale/Open' - pnl.price = round(sl.lot_price_sale,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = self.product - pnl.state = 'fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(sl.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity,4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - elif sl.sale_line.price_type == 'efp': - logger.info("FROM_VALUATION3:%s",sl) - pnl = Pnl() - pnl.purchase = self.purchase.id - pnl.sale = sl.sale_line.sale.id - pnl.line = self.id - pnl.type = 'sale efp' - pnl.date = Date.today() - pnl.lot = sl.id - if sl.lot_type == 'physic': - pnl.reference = 'Sale/Physic' - else: - pnl.reference = 'Sale/Open' - pnl.price = round(sl.lot_price_sale,4) - pnl.counterparty = sl.sale_line.sale.party - pnl.product = self.product - pnl.state = 'not fixed' - mtm = Decimal(0) - pnl.mtm = mtm - pnl.quantity = round(sl.get_current_quantity_converted(),5) - pnl.amount = round(pnl.price * pnl.quantity,4) - pnl.unit = self.unit - pnl.currency = self.purchase.currency - price_lines.append(pnl) - return price_lines - - def group_fees_by_type_supplier(self,fees): - grouped = defaultdict(list) - - # Regrouper par (type, supplier) - for fee in fees: - key = (fee.product, fee.supplier) - grouped[key].append(fee) - result = [] - for key, fee_list in grouped.items(): - ordered_fees = [f for f in fee_list if f.type == 'ordered'] - if ordered_fees: - result.extend(ordered_fees) - else: - budgeted_fees = [f for f in fee_list if f.type == 'budgeted'] - result.extend(budgeted_fees) - return result - - def get_pnl_fee_lines(self): - fee_lines = [] - #pnl management - Pnl = Pool().get('valuation.valuation') - Date = Pool().get('ir.date') - Currency = Pool().get('currency.currency') - FeeLots = Pool().get('fee.lots') - if self.lots: - for lot in self.lots: - fl = FeeLots.search(['lot','=',lot.id]) - if fl: - fees = [e.fee for e in fl] - sorted_fees = self.group_fees_by_type_supplier(fees) - if sorted_fees: - for sf in sorted_fees: - pnl = Pnl() - pnl.lot = lot.id - if lot.sale_line: - pnl.sale = lot.sale_line.sale.id - pnl.purchase = self.purchase.id - pnl.line = self.id - if sf.line: - pnl.type = 'pur. fee' - if sf.sale_line: - pnl.type = 'sale fee' - if sf.shipment_in: - pnl.type = 'shipment fee' - pnl.date = Date.today() - pnl.price = Decimal(sf.get_price_per_qt()) - if sf.currency != self.purchase.currency: - with Transaction().set_context(date=Date.today()): - pnl.price = Currency.compute(sf.currency,pnl.price, self.purchase.currency) - pnl.counterparty = sf.supplier - str_op = '' - if lot.lot_type == 'physic': - str_op = '/Physic' - else: - str_op = '/Open' - pnl.reference = sf.product.name + str_op - pnl.product = sf.product - pnl.state = sf.type - if sf.p_r == 'pay': - sign = -1 - pnl.amount = round(pnl.price * lot.get_current_quantity_converted() * sign,2) - pnl.mtm = 0 - pnl.quantity = round(lot.get_current_quantity_converted(),5) - pnl.unit = sf.unit if sf.unit else self.unit - pnl.currency = sf.currency - fee_lines.append(pnl) - - return fee_lines - def check_from_to(self,tr): if tr.pricing_period: date_from,date_to, d, include, dates = tr.getDateWithEstTrigger(1) @@ -1380,11 +1011,11 @@ class PnlBI(ModelSingleton,ModelSQL, ModelView): config = Configuration.search(['id','>',0])[0] payload = { - "resource": {"dashboard": 6}, + "resource": {"dashboard": 2}, "params": {}, "exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=30), } - token = jwt.encode(payload, "798f256d3119a3292bf121196c2a38dddf2cad155c0b6b0b444efc34c6db197c", algorithm="HS256") + token = jwt.encode(payload, "5d95b70853af02897d1240e2ee4834e2bf065a5132b5d09840fbef6cf683ae45", algorithm="HS256") logger.info("TOKEN:%s",token) if config.dark: url = f"metabase:{config.bi}/embed/dashboard/{token}#theme=night&bordered=true&titled=true" diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 42ecc1b..9926d2c 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -324,14 +324,7 @@ class Sale(metaclass=PoolMeta): if line_p: #compute pnl Pnl = Pool().get('valuation.valuation') - pnl = Pnl.search([('line','=',line_p.id)]) - if pnl: - Pnl.delete(pnl) - pnl_lines = [] - pnl_lines.extend(line_p.get_pnl_fee_lines()) - pnl_lines.extend(line_p.get_pnl_price_lines()) - pnl_lines.extend(line_p.get_pnl_der_lines()) - Pnl.save(pnl_lines) + Pnl.generate(line_p) if line.quantity_theorical: OpenPosition = Pool().get('open.position') @@ -733,14 +726,7 @@ class SaleLine(metaclass=PoolMeta): if purchase_lines: for pl in purchase_lines: Pnl = Pool().get('valuation.valuation') - pnl = Pnl.search([('line','=',pl.id)]) - if pnl: - Pnl.delete(pnl) - pnl_lines = [] - pnl_lines.extend(pl.get_pnl_fee_lines()) - pnl_lines.extend(pl.get_pnl_price_lines()) - pnl_lines.extend(pl.get_pnl_der_lines()) - Pnl.save(pnl_lines) + Pnl.generate(pl) class SaleCreatePurchase(Wizard): "Create mirror purchase" @@ -831,6 +817,20 @@ class Valuation(metaclass=PoolMeta): sale = fields.Many2One('sale.sale',"Sale") sale_line = fields.Many2One('sale.line',"Line") +class ValuationLine(metaclass=PoolMeta): + "Last Valuation" + __name__ = 'valuation.valuation.line' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + +class ValuationReport(metaclass=PoolMeta): + "Valuation Report" + __name__ = 'valuation.report' + + sale = fields.Many2One('sale.sale',"Sale") + sale_line = fields.Many2One('sale.line',"Line") + class ValuationDyn(metaclass=PoolMeta): "Valuation" __name__ = 'valuation.valuation.dyn' diff --git a/modules/purchase_trade/tryton.cfg b/modules/purchase_trade/tryton.cfg index 0ff0286..015d586 100755 --- a/modules/purchase_trade/tryton.cfg +++ b/modules/purchase_trade/tryton.cfg @@ -28,4 +28,5 @@ xml: party.xml forex.xml global_reporting.xml - derivative.xml \ No newline at end of file + derivative.xml + valuation.xml \ No newline at end of file diff --git a/modules/purchase_trade/valuation.py b/modules/purchase_trade/valuation.py new file mode 100644 index 0000000..1c76b56 --- /dev/null +++ b/modules/purchase_trade/valuation.py @@ -0,0 +1,532 @@ +from trytond.model import fields +from trytond.report import Report +from trytond.pool import Pool, PoolMeta +from trytond.pyson import Bool, Eval, Id, If +from trytond.model import (ModelSQL, ModelView) +from trytond.tools import 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 import Column, Literal +from sql.functions import CurrentTimestamp, DateTrunc +from trytond.wizard import Button, StateTransition, StateView, Wizard +from itertools import chain, groupby +from operator import itemgetter +import datetime +import logging +from collections import defaultdict +from trytond.exceptions import UserWarning, UserError + +logger = logging.getLogger(__name__) + +VALTYPE = [ + ('priced', 'Price'), + ('pur. priced', 'Pur. price'), + ('pur. efp', 'Pur. efp'), + ('sale priced', 'Sale price'), + ('sale efp', 'Sale efp'), + ('line fee', 'Line fee'), + ('pur. fee', 'Pur. fee'), + ('sale fee', 'Sale fee'), + ('shipment fee', 'Shipment fee'), + ('market', 'Market'), + ('derivative', 'Derivative'), +] + +class ValuationBase(ModelSQL): + purchase = fields.Many2One('purchase.purchase',"Purchase") + line = fields.Many2One('purchase.line',"Purch. Line") + date = fields.Date("Date") + type = fields.Selection(VALTYPE, "Type") + reference = fields.Char("Reference") + counterparty = fields.Many2One('party.party',"Counterparty") + product = fields.Many2One('product.product',"Product") + state = fields.Char("State") + price = fields.Numeric("Price",digits='unit') + currency = fields.Many2One('currency.currency',"Cur") + quantity = fields.Numeric("Quantity",digits='unit') + unit = fields.Many2One('product.uom',"Unit") + amount = fields.Numeric("Amount",digits='unit') + mtm = fields.Numeric("Mtm",digits='unit') + lot = fields.Many2One('lot.lot',"Lot") + base_amount = fields.Numeric("Base Amount",digits='unit') + rate = fields.Numeric("Rate", digits=(16,6)) + + @classmethod + def _base_pnl(cls, *, line, lot, pnl_type, sale=None): + Date = Pool().get('ir.date') + + values = { + 'purchase': line.purchase.id, + 'line': line.id, + 'type': pnl_type, + 'date': Date.today(), + 'lot': lot.id, + } + + if sale: + values['sale'] = sale.id + + return values + + @classmethod + def _build_basis_pnl(cls, *, line, lot, sale_line, pc, sign): + Currency = Pool().get('currency.currency') + Date = Pool().get('ir.date') + values = cls._base_pnl( + line=line, + lot=lot, + sale=sale_line.sale if sale_line else None, + pnl_type='sale priced' if sale_line else 'pur. priced' + ) + + qty = lot.get_current_quantity_converted() + + values.update({ + 'reference': f"{pc.get_name()} / {pc.ratio}%", + 'price': round(pc.price, 4), + 'counterparty': sale_line.sale.party.id if sale_line else line.purchase.party.id, + 'product': sale_line.product.id if sale_line else line.product.id, + }) + + # State + if pc.unfixed_qt == 0: + values['state'] = 'fixed' + elif pc.fixed_qt == 0: + values['state'] = 'unfixed' + else: + base = sale_line.quantity_theorical if sale_line else line.quantity_theorical + values['state'] = f"part. fixed {round(pc.fixed_qt / Decimal(base) * 100, 0)}%" + + if pc.price and pc.ratio: + amount = round(pc.price * qty * Decimal(sign) * pc.ratio / 100, 4) + base_amount = amount + currency = sale_line.sale.currency.id if sale_line else line.purchase.currency.id + rate = Decimal(1) + if line.purchase.company.currency != currency: + with Transaction().set_context(date=Date.today()): + base_amount = Currency.compute(currency,amount, line.purchase.company.currency) + rate = round(amount / base_amount,6) + last_price = pc.get_last_price() + mtm = round(Decimal(last_price) * qty * Decimal(sign), 4) if last_price else Decimal(0) + + values.update({ + 'quantity': round(qty, 5), + 'amount': amount, + 'base_amount': base_amount, + 'rate': rate, + 'mtm': round(amount - (mtm * pc.ratio / 100), 4), + 'unit': sale_line.unit.id if sale_line else line.unit.id, + 'currency': currency, + }) + + return values + + @classmethod + def _build_simple_pnl(cls, *, line, lot, sale_line, price, state, sign, pnl_type): + Currency = Pool().get('currency.currency') + Date = Pool().get('ir.date') + values = cls._base_pnl( + line=line, + lot=lot, + sale=sale_line.sale if sale_line else None, + pnl_type=pnl_type + ) + + qty = lot.get_current_quantity_converted() + amount = round(price * qty * Decimal(sign), 4) + base_amount = amount + currency = sale_line.sale.currency.id if sale_line else line.purchase.currency.id + company_currency = sale_line.sale.company.currency if sale_line else line.purchase.company.currency + rate = Decimal(1) + if line.purchase.company.currency != currency: + with Transaction().set_context(date=Date.today()): + base_amount = Currency.compute(currency,amount, company_currency) + if base_amount and amount: + rate = round(amount / base_amount,6) + + values.update({ + 'price': round(price, 4), + 'quantity': round(qty, 5), + 'amount': amount, + 'base_amount': base_amount, + 'rate': rate, + 'mtm': Decimal(0), + 'state': state, + 'unit': sale_line.unit.id if sale_line else line.unit.id, + 'currency': currency, + 'counterparty': sale_line.sale.party.id if sale_line else line.purchase.party.id, + 'product': sale_line.product.id if sale_line else line.product.id, + 'reference': ( + 'Sale/Physic' if lot.lot_type == 'physic' + else 'Sale/Open' if sale_line + else 'Purchase/Physic' + ), + }) + + return values + + @classmethod + def create_pnl_price_from_line(cls, line): + price_lines = [] + LotQt = Pool().get('lot.qt') + + for lot in line.lots: + + if line.price_type == 'basis': + for pc in line.price_summary or []: + values = cls._build_basis_pnl(line=line, lot=lot, sale_line=None, pc=pc, sign=-1) + if values: + price_lines.append(values) + + elif line.price_type in ('priced', 'efp') and lot.lot_price: + price_lines.append( + cls._build_simple_pnl( + line=line, + lot=lot, + sale_line=None, + price=lot.lot_price, + state='fixed' if line.price_type == 'priced' else 'not fixed', + sign=-1, + pnl_type=f'pur. {line.price_type}' + ) + ) + + sale_lots = [lot] if lot.sale_line else [ + lqt.lot_s for lqt in LotQt.search([ + ('lot_p', '=', lot.id), + ('lot_s', '>', 0), + ('lot_quantity', '>', 0), + ]) + ] + + for sl in sale_lots: + sl_line = sl.sale_line + if not sl_line: + continue + + if sl_line.price_type == 'basis': + for pc in sl_line.price_summary or []: + values = cls._build_basis_pnl(line=line, lot=sl, sale_line=sl_line, pc=pc, sign=+1) + if values: + price_lines.append(values) + + elif sl_line.price_type in ('priced', 'efp'): + price_lines.append( + cls._build_simple_pnl( + line=line, + lot=sl, + sale_line=sl_line, + price=sl.lot_price_sale, + state='fixed' if sl_line.price_type == 'priced' else 'not fixed', + sign=+1, + pnl_type=f'sale {sl_line.price_type}' + ) + ) + + return price_lines + + + @classmethod + def group_fees_by_type_supplier(cls,line,fees): + grouped = defaultdict(list) + + # Regrouper par (type, supplier) + for fee in fees: + key = (fee.product, fee.supplier) + grouped[key].append(fee) + result = [] + for key, fee_list in grouped.items(): + ordered_fees = [f for f in fee_list if f.type == 'ordered'] + if ordered_fees: + result.extend(ordered_fees) + else: + budgeted_fees = [f for f in fee_list if f.type == 'budgeted'] + result.extend(budgeted_fees) + return result + + @classmethod + def create_pnl_fee_from_line(cls, line): + fee_lines = [] + Date = Pool().get('ir.date') + Currency = Pool().get('currency.currency') + FeeLots = Pool().get('fee.lots') + + for lot in line.lots or []: + fl = FeeLots.search([('lot', '=', lot.id)]) + if not fl: + continue + + fees = [e.fee for e in fl] + for sf in cls.group_fees_by_type_supplier(line, fees): + + price = Decimal(sf.get_price_per_qt()) + if sf.currency != line.purchase.currency: + with Transaction().set_context(date=Date.today()): + price = Currency.compute(sf.currency, price, line.purchase.currency) + + sign = -1 if sf.p_r == 'pay' else 1 + + fee_lines.append({ + 'lot': lot.id, + 'sale': lot.sale_line.sale.id if lot.sale_line else None, + 'purchase': line.purchase.id, + 'line': line.id, + 'type': ( + 'shipment fee' if sf.shipment_in + else 'sale fee' if sf.sale_line + else 'pur. fee' + ), + 'date': Date.today(), + 'price': price, + 'counterparty': sf.supplier.id, + 'reference': f"{sf.product.name}/{'Physic' if lot.lot_type == 'physic' else 'Open'}", + 'product': sf.product.id, + 'state': sf.type, + 'quantity': round(lot.get_current_quantity_converted(), 5), + 'amount': round(price * lot.get_current_quantity_converted() * sign, 2), + 'mtm': Decimal(0), + 'unit': sf.unit.id if sf.unit else line.unit.id, + 'currency': sf.currency.id, + }) + + return fee_lines + + @classmethod + def create_pnl_der_from_line(cls, line): + Date = Pool().get('ir.date') + der_lines = [] + + for d in line.derivatives or []: + price = Decimal(d.price_index.get_price_per_qt( + d.price, line.unit, line.purchase.currency + )) + + mtm = Decimal(d.price_index.get_price( + Date.today(), line.unit, line.purchase.currency, True + )) + + der_lines.append({ + 'purchase': line.purchase.id, + 'line': line.id, + 'type': 'derivative', + 'date': Date.today(), + 'reference': d.price_index.price_index, + 'price': round(price, 4), + 'counterparty': d.party.id, + 'product': d.product.id, + 'state': 'fixed', + 'quantity': round(d.quantity, 5), + 'amount': round(price * d.quantity * Decimal(-1), 4), + 'mtm': round((price * d.quantity * Decimal(-1)) - (mtm * d.quantity * Decimal(-1)), 4), + 'unit': line.unit.id, + 'currency': line.purchase.currency.id, + }) + + return der_lines + + @classmethod + def generate(cls, line): + Date = Pool().get('ir.date') + Valuation = Pool().get('valuation.valuation') + ValuationLine = Pool().get('valuation.valuation.line') + + Valuation.delete(Valuation.search([ + ('line', '=', line.id), + ('date', '=', Date.today()), + ])) + + ValuationLine.delete(ValuationLine.search([ + ('line', '=', line.id), + ])) + + values = [] + values.extend(cls.create_pnl_fee_from_line(line)) + values.extend(cls.create_pnl_price_from_line(line)) + values.extend(cls.create_pnl_der_from_line(line)) + + Valuation.create(values) + ValuationLine.create(values) + +class Valuation(ValuationBase, ModelView): + "Valuation" + __name__ = 'valuation.valuation' + + @classmethod + def get_totals(cls): + cursor = Transaction().connection.cursor() + table = cls.__table__() + + sql = f""" + WITH ranked AS ( + SELECT + CASE + WHEN line IS NOT NULL THEN 'P:' || line::text + WHEN sale_line IS NOT NULL THEN 'S:' || sale_line::text + END AS block_key, + date, + amount, + ROW_NUMBER() OVER ( + PARTITION BY + CASE + WHEN line IS NOT NULL THEN 'P:' || line::text + WHEN sale_line IS NOT NULL THEN 'S:' || sale_line::text + END + ORDER BY date DESC + ) AS rn + FROM {table} + WHERE line IS NOT NULL + OR sale_line IS NOT NULL + ), + current_prev AS ( + SELECT + block_key, + MAX(CASE WHEN rn = 1 THEN amount END) AS amount_t, + MAX(CASE WHEN rn = 2 THEN amount END) AS amount_t1 + FROM ranked + WHERE rn <= 2 + GROUP BY block_key + ) + SELECT + COALESCE(SUM(amount_t), 0) AS total_t, + COALESCE(SUM(amount_t1), 0) AS total_t1, + COALESCE(SUM(amount_t), 0) + - COALESCE(SUM(amount_t1), 0) AS variation + FROM current_prev + """ + + cursor.execute(sql) + total_t, total_t1, variation = cursor.fetchone() + + return total_t, total_t1, variation + +class ValuationLine(ValuationBase, ModelView): + "Last Valuation" + __name__ = 'valuation.valuation.line' + +class ValuationDyn(ModelSQL,ModelView): + "Valuation" + __name__ = 'valuation.valuation.dyn' + + r_purchase = fields.Many2One('purchase.purchase',"Purchase") + r_line = fields.Many2One('purchase.line',"Line") + r_date = fields.Date("Date") + r_type = fields.Selection(VALTYPE, "Type") + r_reference = fields.Char("Reference") + r_counterparty = fields.Many2One('party.party',"Counterparty") + r_product = fields.Many2One('product.product',"Product") + r_state = fields.Char("State") + r_price = fields.Numeric("Price",digits='r_unit') + r_currency = fields.Many2One('currency.currency',"Cur") + r_quantity = fields.Numeric("Quantity",digits='r_unit') + r_unit = fields.Many2One('product.uom',"Unit") + 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 = fields.Numeric("Mtm",digits='r_unit') + r_lot = fields.Many2One('lot.lot',"Lot") + + @classmethod + def table_query(cls): + Valuation = Pool().get('valuation.valuation.line') + val = Valuation.__table__() + context = Transaction().context + group_pnl = context.get('group_pnl') + wh = (val.id > 0) + + query = val.select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + Max(val.id).as_('id'), + Max(val.purchase).as_('r_purchase'), + Max(val.line).as_('r_line'), + Max(val.date).as_('r_date'), + val.type.as_('r_type'), + Max(val.reference).as_('r_reference'), + val.counterparty.as_('r_counterparty'), + Max(val.product).as_('r_product'), + val.state.as_('r_state'), + Avg(val.price).as_('r_price'), + 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.lot).as_('r_lot'), + where=wh, + group_by=[val.type,val.counterparty,val.state]) + + return query + +class ValuationReport(ValuationBase, ModelView): + "Valuation Report" + __name__ = 'valuation.report' + + @classmethod + def table_query(cls): + Valuation = Pool().get('valuation.valuation') + val = Valuation.__table__() + context = Transaction().context + valuation_date = context.get('valuation_date') + wh = (val.date == valuation_date) + + query = val.select( + Literal(0).as_('create_uid'), + CurrentTimestamp().as_('create_date'), + Literal(None).as_('write_uid'), + Literal(None).as_('write_date'), + val.id.as_('id'), + val.purchase.as_('purchase'), + val.sale.as_('sale'), + val.sale_line.as_('sale_line'), + val.line.as_('line'), + val.date.as_('date'), + val.type.as_('type'), + val.reference.as_('reference'), + val.counterparty.as_('counterparty'), + val.product.as_('product'), + val.state.as_('state'), + val.price.as_('price'), + val.currency.as_('currency'), + val.quantity.as_('quantity'), + val.unit.as_('unit'), + val.amount.as_('amount'), + val.base_amount.as_('base_amount'), + val.rate.as_('rate'), + val.mtm.as_('mtm'), + val.lot.as_('lot'), + where=wh) + + return query + +class ValuationReportContext(ModelView): + "Valuation Report Context" + __name__ = 'valuation.report.context' + + valuation_date = fields.Date("Valuation date") + 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_valuation_date(cls): + pool = Pool() + Date = pool.get('ir.date') + return Date.today() + + @classmethod + def default_state(cls): + return 'all' diff --git a/modules/purchase_trade/valuation.xml b/modules/purchase_trade/valuation.xml new file mode 100644 index 0000000..e95de26 --- /dev/null +++ b/modules/purchase_trade/valuation.xml @@ -0,0 +1,51 @@ + + + + valuation.valuation + tree + valuation_tree_sequence3 + + + valuation.valuation + graph + valuation_graph + + + valuation.valuation + graph + valuation_graph2 + + + valuation.valuation.dyn + tree + valuation_tree_sequence4 + + + + valuation.report.context + form + valuation_context_form + + + valuation.report + tree + valuation_list + + + Valuation + valuation.report + valuation.report.context + + + + + + + + + + \ No newline at end of file diff --git a/modules/purchase_trade/view/gr_configuration_form.xml b/modules/purchase_trade/view/gr_configuration_form.xml index 0d8c1cc..672251f 100644 --- a/modules/purchase_trade/view/gr_configuration_form.xml +++ b/modules/purchase_trade/view/gr_configuration_form.xml @@ -1,6 +1,8 @@