Merge pull request 'Merge dev from main' (#1) from main into dev
Reviewed-on: https://srv413259.hstgr.cloud/admin/tradon/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 "📉"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,26 +19,6 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<field name="name">fee_tree_sequence</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="valuation_view_tree_sequence3">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">valuation_tree_sequence3</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_graph">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">valuation_graph</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_graph2">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">valuation_graph2</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_tree_sequence4">
|
||||
<field name="model">valuation.valuation.dyn</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">valuation_tree_sequence4</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="fee_view_tree_sequence2">
|
||||
<field name="model">fee.fee</field>
|
||||
<field name="type">tree</field>
|
||||
|
||||
@@ -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")
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -28,4 +28,5 @@ xml:
|
||||
party.xml
|
||||
forex.xml
|
||||
global_reporting.xml
|
||||
derivative.xml
|
||||
derivative.xml
|
||||
valuation.xml
|
||||
532
modules/purchase_trade/valuation.py
Normal file
532
modules/purchase_trade/valuation.py
Normal file
@@ -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'
|
||||
51
modules/purchase_trade/valuation.xml
Normal file
51
modules/purchase_trade/valuation.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<tryton>
|
||||
<data>
|
||||
<record model="ir.ui.view" id="valuation_view_tree_sequence3">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">valuation_tree_sequence3</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_graph">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">valuation_graph</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_graph2">
|
||||
<field name="model">valuation.valuation</field>
|
||||
<field name="type">graph</field>
|
||||
<field name="name">valuation_graph2</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_tree_sequence4">
|
||||
<field name="model">valuation.valuation.dyn</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">valuation_tree_sequence4</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="valuation_context_view_form">
|
||||
<field name="model">valuation.report.context</field>
|
||||
<field name="type">form</field>
|
||||
<field name="name">valuation_context_form</field>
|
||||
</record>
|
||||
<record model="ir.ui.view" id="valuation_view_list">
|
||||
<field name="model">valuation.report</field>
|
||||
<field name="type">tree</field>
|
||||
<field name="name">valuation_list</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window" id="act_valuation_form">
|
||||
<field name="name">Valuation</field>
|
||||
<field name="res_model">valuation.report</field>
|
||||
<field name="context_model">valuation.report.context</field>
|
||||
</record>
|
||||
<record model="ir.action.act_window.view" id="act_valuation_form_view">
|
||||
<field name="sequence" eval="70"/>
|
||||
<field name="view" ref="valuation_view_list"/>
|
||||
<field name="act_window" ref="act_valuation_form"/>
|
||||
</record>
|
||||
|
||||
<menuitem
|
||||
parent="purchase_trade.menu_global_reporting"
|
||||
sequence="120"
|
||||
action="act_valuation_form"
|
||||
id="menu_valuation_form"/>
|
||||
</data>
|
||||
</tryton>
|
||||
@@ -1,6 +1,8 @@
|
||||
<form>
|
||||
<label name="bi"/>
|
||||
<field name="bi"/>
|
||||
<label name="dashboard"/>
|
||||
<field name="dashboard"/>
|
||||
<label name="dark"/>
|
||||
<field name="dark"/>
|
||||
</form>
|
||||
16
modules/purchase_trade/view/valuation_context_form.xml
Normal file
16
modules/purchase_trade/view/valuation_context_form.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<form>
|
||||
<label name="valuation_date"/>
|
||||
<field name="valuation_date"/>
|
||||
<label name="supplier"/>
|
||||
<field name="supplier"/>
|
||||
<label name="client"/>
|
||||
<field name="client"/>
|
||||
<label name="purchase"/>
|
||||
<field name="purchase"/>
|
||||
<label name="sale"/>
|
||||
<field name="sale"/>
|
||||
<label name="product"/>
|
||||
<field name="product"/>
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
</form>
|
||||
15
modules/purchase_trade/view/valuation_list.xml
Normal file
15
modules/purchase_trade/view/valuation_list.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<tree>
|
||||
<field name="lot"/>
|
||||
<field name="purchase"/>
|
||||
<field name="sale"/>
|
||||
<field name="type"/>
|
||||
<field name="reference"/>
|
||||
<field name="counterparty"/>
|
||||
<field name="state"/>
|
||||
<field name="price"/>
|
||||
<field name="quantity" symbol="unit"/>
|
||||
<field name="amount"/>
|
||||
<field name="base_amount" sum="1"/>
|
||||
<field name="rate"/>
|
||||
<field name="mtm" optional="1" sum="1"/>
|
||||
</tree>
|
||||
Reference in New Issue
Block a user