From 914fecf3a0269f4398a2195068b7d06dabd84d98 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Fri, 24 Jul 2026 15:52:00 +0200 Subject: [PATCH] Purchase form --- modules/purchase_trade/purchase.py | 166 +++++++++++++++++- modules/purchase_trade/view/purchase_form.xml | 22 +-- 2 files changed, 168 insertions(+), 20 deletions(-) diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py index 974b0da..1fdf136 100755 --- a/modules/purchase_trade/purchase.py +++ b/modules/purchase_trade/purchase.py @@ -383,6 +383,8 @@ class Purchase(metaclass=PoolMeta): viewer = fields.Function(fields.Text(""),'get_viewer') execution_summary_viewer = fields.Function( fields.Text(""), 'get_execution_summary_viewer') + purchase_summary_data = fields.Function( + fields.Text("Purchase Summary"), 'get_purchase_summary_data') doc_template = fields.Many2One('doc.template',"Template") required_documents = fields.Many2Many( 'contract.document.type', 'purchase', 'doc_type', 'Required Documents') @@ -965,6 +967,48 @@ class Purchase(metaclass=PoolMeta): return 0.0 def get_execution_summary_viewer(self, name=None): + data = self._get_purchase_execution_summary_data() + data = dict(data, type='execution-summary') + return "d3:" + json.dumps(data) + + @staticmethod + def _summary_number(value): + if value is None: + return None + try: + return float(value) + except (TypeError, ValueError): + return None + + def _get_purchase_map_summary_data(self): + dep_name = '' + arr_name = '' + departure = None + arrival = None + if self.from_location: + dep_name = self.from_location.name or self.from_location.rec_name + departure = { + "name": dep_name, + "lat": self._summary_number(self.from_location.lat), + "lon": self._summary_number(self.from_location.lon), + } + if self.to_location: + arr_name = self.to_location.name or self.to_location.rec_name + arrival = { + "name": arr_name, + "lat": self._summary_number(self.to_location.lat), + "lon": self._summary_number(self.to_location.lon), + } + route = '%s > %s' % (dep_name or '-', arr_name or '-') + return { + "from": dep_name, + "to": arr_name, + "route": route, + "departures": [departure] if departure else [], + "arrivals": [arrival] if arrival else [], + } + + def _get_purchase_execution_summary_data(self): LotQt = Pool().get('lot.qt') rows = [] totals = defaultdict(float) @@ -1064,13 +1108,117 @@ class Purchase(metaclass=PoolMeta): totals['open'] += max(quantity - shipped, 0.0) totals['lines'] += 1 - data = { - 'type': 'execution-summary', + return { 'unit': rows[0]['unit'] if rows else '', 'totals': dict(totals), 'rows': rows[:4], } - return "d3:" + json.dumps(data) + + @staticmethod + def _json_decimal(value): + if value in (None, ''): + return None + return float(value) + + def _get_purchase_pnl_summary_data(self): + currency = getattr(getattr(self, 'currency', None), 'code', None) or '' + graph = list(getattr(self, 'pnl_graph', None) or []) + graph = sorted( + graph, key=lambda line: ( + getattr(line, 'valuation_date', None) or datetime.date.min, + getattr(line, 'id', 0) or 0)) + if graph: + current = graph[-1] + previous = graph[-2] if len(graph) > 1 else None + pnl_value = getattr(current, 'pnl', None) + mtm_value = getattr(current, 'mtm', None) + previous_pnl = getattr(previous, 'pnl', None) if previous else None + previous_mtm = getattr(previous, 'mtm', None) if previous else None + pnl_change = ( + pnl_value - previous_pnl + if pnl_value is not None and previous_pnl is not None + else None) + mtm_change = ( + mtm_value - previous_mtm + if mtm_value is not None and previous_mtm is not None + else None) + return { + "pnl": { + "value": self._json_decimal(pnl_value), + "currency": currency, + "change_d1": self._json_decimal(pnl_change), + }, + "mtm": { + "value": self._json_decimal(mtm_value), + "currency": currency, + "change": self._json_decimal(mtm_change), + }, + } + + pnl_value = Decimal('0') + mtm_value = Decimal('0') + if self.group_pnl: + lines = getattr(self, 'pnl', None) or [] + for line in lines: + pnl_value += Decimal(str(getattr(line, 'r_pnl', 0) or 0)) + mtm_value += Decimal(str(getattr(line, 'r_mtm', 0) or 0)) + else: + lines = getattr(self, 'pnl_', None) or [] + for line in lines: + pnl_value += Decimal(str(getattr(line, 'pnl', 0) or 0)) + mtm_value += Decimal(str(getattr(line, 'mtm', 0) or 0)) + return { + "pnl": { + "value": self._json_decimal(pnl_value), + "currency": currency, + "change_d1": None, + }, + "mtm": { + "value": self._json_decimal(mtm_value), + "currency": currency, + "change": None, + }, + } + + def _get_purchase_activity_summary_data(self): + if not getattr(self, 'id', None): + return [] + Log = Pool().get('ir.model.log') + logs = Log.search([ + ('resource', '=', 'purchase.purchase,%s' % self.id), + ], limit=4) + event_types = { + 'write': 'info', + 'transition': 'approved', + 'button': 'success', + 'wizard': 'info', + 'delete': 'warning', + } + activity = [] + for log in logs: + date = getattr(log, 'create_date', None) + user = getattr(getattr(log, 'user', None), 'rec_name', None) or '' + text = getattr(log, 'action', None) or getattr(log, 'target', None) + if not text: + text = getattr(log, 'event_string', None) or log.event + activity.append({ + "type": event_types.get(log.event, 'info'), + "text": text, + "date": date.strftime('%d/%m/%Y') if date else '', + "user": user, + }) + return activity + + def get_purchase_summary_data(self, name=None): + data = { + "currency": getattr(getattr(self, 'currency', None), 'code', None) + or '', + "map": self._get_purchase_map_summary_data(), + "execution": self._get_purchase_execution_summary_data(), + "activity": self._get_purchase_activity_summary_data(), + } + data.update(self._get_purchase_pnl_summary_data()) + return json.dumps(data) def getLots(self): if self.lines: @@ -1078,9 +1226,15 @@ class Purchase(metaclass=PoolMeta): return [l for l in self.lines.lots] - @fields.depends('party','from_location','to_location') - def on_change_with_viewer(self): - return self.get_viewer() + @fields.depends('party','from_location','to_location') + def on_change_with_viewer(self): + return self.get_viewer() + + @fields.depends( + 'currency', 'from_location', 'to_location', 'lines', 'pnl', 'pnl_', + 'pnl_graph', 'group_pnl') + def on_change_with_purchase_summary_data(self, name=None): + return self.get_purchase_summary_data() @fields.depends('doc_template','required_documents') def on_change_with_required_documents(self): diff --git a/modules/purchase_trade/view/purchase_form.xml b/modules/purchase_trade/view/purchase_form.xml index 16fea73..07ed28c 100755 --- a/modules/purchase_trade/view/purchase_form.xml +++ b/modules/purchase_trade/view/purchase_form.xml @@ -22,20 +22,14 @@ this repository contains the full copyright notices and license terms. --> - - - - - - - + +