Purchase form

This commit is contained in:
2026-07-24 15:52:00 +02:00
parent 94bbed79f0
commit 914fecf3a0
2 changed files with 168 additions and 20 deletions

View File

@@ -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):

View File

@@ -22,20 +22,14 @@ this repository contains the full copyright notices and license terms. -->
<field name="currency"/>
</group>
<group id="purchase_header_visuals" colspan="6" col="2"
xalign="0" yalign="0" col_widths="1fr,1fr">
<group id="purchase_map" string="Map" colspan="1" col="1"
panel="card" icon="tryton-public" xalign="0" yalign="0"
col_widths="1fr">
<field name="viewer" widget="html_viewer" height="120"/>
</group>
<group id="purchase_recap" string="Execution summary"
colspan="1" col="1" panel="card" icon="tryton-note"
xalign="0" yalign="0"
col_widths="1fr">
<field name="execution_summary_viewer" widget="html_viewer"
height="120"/>
</group>
<group id="purchase_header_visuals" colspan="6" col="4"
string="Purchase summary" icon="tryton-dashboard"
panel="card" xalign="0" yalign="0"
col_widths="1fr,1fr,1fr,1fr">
<field name="purchase_summary_data"
widget="purchase_summary"
colspan="4"
height="168"/>
</group>
<group id="purchase_header_terms_people" colspan="6" col="2"