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 @@
\ No newline at end of file
diff --git a/modules/purchase_trade/view/valuation_context_form.xml b/modules/purchase_trade/view/valuation_context_form.xml
new file mode 100644
index 0000000..8ec7022
--- /dev/null
+++ b/modules/purchase_trade/view/valuation_context_form.xml
@@ -0,0 +1,16 @@
+
diff --git a/modules/purchase_trade/view/valuation_list.xml b/modules/purchase_trade/view/valuation_list.xml
new file mode 100644
index 0000000..f5bce40
--- /dev/null
+++ b/modules/purchase_trade/view/valuation_list.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file