Pnl graph

This commit is contained in:
2026-07-08 14:38:36 +02:00
parent 3122255bc7
commit 858b7258d9
4 changed files with 87 additions and 21 deletions

View File

@@ -185,12 +185,13 @@ def register():
derivative.DerivativeReportContext,
fee.FeeReport,
fee.FeeContext,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
forex.ForexCoverPhysicalContract,
forex.PForex,
forex.ForexBI,
purchase.PurchasePnlGraph,
purchase.PnlBI,
purchase.PositionBI,
stock.Move,
stock.Location,
stock.InvoiceLine,
stock.ShipmentIn,

View File

@@ -11,7 +11,7 @@ from trytond.tools import (cursor_dict, is_full_text, lstrip_wildcard)
from trytond.transaction import Transaction, inactive_records
from decimal import getcontext, Decimal, ROUND_HALF_UP
from sql.aggregate import Count, Max, Min, Sum, Avg, BoolOr
from sql.conditionals import Case
from sql.conditionals import Case, Coalesce
from sql import Column, Literal
from sql.functions import CurrentTimestamp, DateTrunc, Abs
from trytond.wizard import Button, StateTransition, StateView, Wizard, StateAction
@@ -370,11 +370,13 @@ class Purchase(metaclass=PoolMeta):
wb = fields.Many2One('purchase.weight.basis',"Weight basis", required=True)
association = fields.Many2One('purchase.association',"Association", required=True,states={'invisible': Eval('company_visible'),})
crop = fields.Many2One('purchase.crop',"Crop",states={'invisible': Eval('company_visible'),})
pnl = fields.One2Many('valuation.valuation.dyn', 'r_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)
pnl = fields.One2Many('valuation.valuation.dyn', 'r_purchase', 'Pnl',states={'invisible': ~Eval('group_pnl'),})
pnl_ = fields.One2Many('valuation.valuation.line', 'purchase', 'Pnl',states={'invisible': Eval('group_pnl'),})
pnl_graph = fields.One2Many(
'purchase.pnl.graph', 'purchase', 'Pnl graph')
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)
plan = fields.Many2One('workflow.plan',"Name")
estimated_date = fields.One2Many('pricing.estimated','purchase',"Estimated date")
group_pnl = fields.Boolean("Group Pnl")
@@ -3319,9 +3321,9 @@ class GoToBi(Wizard):
action['url'] = config.bi + '/dashboard/6-pnl?lot=&product=&purchase='+ ct_number + '&sale='
return action, {}
class PurchaseAllocationsWizard(Wizard):
'Open Allocations report from Purchase without modal'
__name__ = 'purchase.allocations.wizard'
class PurchaseAllocationsWizard(Wizard):
'Open Allocations report from Purchase without modal'
__name__ = 'purchase.allocations.wizard'
start_state = 'open_report'
@@ -3334,11 +3336,45 @@ class PurchaseAllocationsWizard(Wizard):
action['context_model'] = 'lot.context'
action['pyson_context'] = PYSONEncoder().encode({
'purchase': purchase_id,
})
return action, {}
class PurchaseInvoiceReport(
ModelSQL, ModelView):
})
return action, {}
class PurchasePnlGraph(ModelSQL, ModelView):
"Purchase Pnl graph"
__name__ = 'purchase.pnl.graph'
purchase = fields.Many2One('purchase.purchase', "Purchase")
valuation_date = fields.Date("Valuation Date")
pnl = fields.Numeric("Pnl", digits=(16, 2))
mtm = fields.Numeric("Mtm", digits=(16, 2))
@classmethod
def __setup__(cls):
super().__setup__()
cls._order.insert(0, ('valuation_date', 'ASC'))
@classmethod
def table_query(cls):
Valuation = Pool().get('valuation.valuation')
val = Valuation.__table__()
return 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'),
val.purchase.as_('purchase'),
val.date.as_('valuation_date'),
Sum(Coalesce(val.pnl, 0)).as_('pnl'),
Sum(Coalesce(val.mtm, 0)).as_('mtm'),
where=(val.purchase != None),
group_by=[val.purchase, val.date],
order_by=[val.date.asc])
class PurchaseInvoiceReport(
ModelSQL, ModelView):
"Purchase invoices"
__name__ = 'purchase.invoice.report'
r_supplier = fields.Many2One('party.party',"Supplier")

View File

@@ -2,6 +2,7 @@
# this repository contains the full copyright notices and license terms.
import datetime
from pathlib import Path
from contextlib import nullcontext
from decimal import Decimal
from types import SimpleNamespace
@@ -5252,6 +5253,24 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(
ShipmentIn.operator.domain, [('categories.name', '=', 'OPERATOR')])
def test_purchase_form_has_pnl_graph_tab_after_pnl(self):
'purchase form exposes a rich graph for historical PnL and MTM'
path = Path('modules/purchase_trade/view/purchase_form.xml')
root = ElementTree.parse(path).getroot()
pages = root.findall(".//xpath[@expr=\"/form/notebook/page[@id='info']\"]//page")
page_ids = [page.get('id') for page in pages]
pnl_index = page_ids.index('pnl')
self.assertEqual(page_ids[pnl_index + 1], 'pnl_graph')
graph_field = pages[pnl_index + 1].find("field[@name='pnl_graph']")
self.assertIsNotNone(graph_field)
self.assertEqual(graph_field.get('widget'), 'rich_graph')
self.assertEqual(graph_field.get('date_field'), 'valuation_date')
self.assertEqual(graph_field.get('series'), 'pnl,mtm')
self.assertEqual(graph_field.get('labels'), 'pnl:Pnl,mtm:Mtm')
self.assertIsNone(graph_field.get('hidden_series'))
def test_shipment_in_pnl_lines_use_physical_and_open_lots(self):
'shipment PnL uses valuation lines from physical and open lot links'
ShipmentIn = Pool().get('stock.shipment.in')

View File

@@ -149,13 +149,23 @@ this repository contains the full copyright notices and license terms. -->
<page string="Forex" col="4" id="fo">
<field name="forex" mode="tree" view_ids="purchase_trade.view_forex_cover_physical_contract_tree2"/>
</page>
<page string="Pnl" col="4" >
<page string="Pnl" col="4" id="pnl">
<label name="group_pnl"/>
<field name="group_pnl" colspan="1"/>
<newline/>
<field name="pnl_" mode="tree,graph,graph" view_ids="purchase_trade.valuation_view_tree_sequence3,purchase_trade.valuation_view_graph,purchase_trade.valuation_view_graph2" height="450" colspan="4"/>
<field name="pnl" mode="tree" view_ids="purchase_trade.valuation_view_tree_sequence4" height="450" colspan="4"/>
</page>
<page string="Pnl graph" col="4" id="pnl_graph">
<field name="pnl_graph"
widget="rich_graph"
date_field="valuation_date"
series="pnl,mtm"
labels="pnl:Pnl,mtm:Mtm"
colors="pnl:#00b894,mtm:#8b5cf6"
height="360"
colspan="4"/>
</page>
</xpath>
<xpath expr="/form/notebook/page[@id='info']/label[@name='invoice_party']" position="before">
<field name="bank_accounts" colspan="4" invisible="1"/>