From 3a6f4260339abd5d63499bef039fa3d3b21c5310 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Mon, 8 Jun 2026 21:44:08 +0200 Subject: [PATCH] Sale design --- modules/purchase_trade/sale.py | 119 ++++++++++++++++ modules/purchase_trade/view/sale_form.xml | 157 +++++++++++++++------- 2 files changed, 225 insertions(+), 51 deletions(-) diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index afb4892..d35e60b 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -15,6 +15,7 @@ from sql.functions import CurrentTimestamp, DateTrunc import datetime import logging import json +from collections import defaultdict from trytond.exceptions import UserWarning, UserError from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words, format_date_en @@ -279,6 +280,8 @@ class Sale(metaclass=PoolMeta): association = fields.Many2One('purchase.association',"Association", required=True,states={'invisible': Eval('company_visible'),}) crop = fields.Many2One('purchase.crop',"Crop",states={'invisible': Eval('company_visible'),}) viewer = fields.Function(fields.Text(""),'get_viewer') + execution_summary_viewer = fields.Function( + fields.Text(""), 'get_execution_summary_viewer') doc_template = fields.Many2One('doc.template',"Template") required_documents = fields.Many2Many( 'contract.document.type', 'sale', 'doc_type', 'Required Documents') @@ -1186,6 +1189,122 @@ class Sale(metaclass=PoolMeta): def on_change_with_viewer(self): return self.get_viewer() + def _execution_summary_quantity(self, value): + if value is None: + return 0.0 + try: + return float(value) + except (TypeError, ValueError): + return 0.0 + + def get_execution_summary_viewer(self, name=None): + LotQt = Pool().get('lot.qt') + rows = [] + totals = defaultdict(float) + + for line in (self.lines or []): + if getattr(line, 'type', 'line') != 'line': + continue + + quantity = self._execution_summary_quantity( + getattr(line, 'quantity_theorical', None) + or getattr(line, 'quantity', None)) + matched = 0.0 + shipped = 0.0 + lots_count = 0 + shipped_routes = [] + line_lots = list(getattr(line, 'lots', None) or []) + lot_ids = [lot.id for lot in line_lots if getattr(lot, 'id', None)] + lot_qts = LotQt.search([('lot_s', 'in', lot_ids)]) if lot_ids else [] + + for lqt in lot_qts: + lots_count += 1 + lot_quantity = self._execution_summary_quantity( + getattr(lqt, 'lot_quantity', None) + or getattr(lqt, 'lot_qt', None)) + if getattr(lqt, 'lot_p', None): + matched += lot_quantity + shipment = ( + getattr(lqt, 'lot_shipment_in', None) + or getattr(lqt, 'lot_shipment_internal', None) + or getattr(lqt, 'lot_shipment_out', None)) + if shipment: + shipped += lot_quantity + route_from = getattr( + getattr(shipment, 'from_location', None), + 'rec_name', None) + route_to = getattr( + getattr(shipment, 'to_location', None), + 'rec_name', None) + route_from = route_from or getattr( + getattr(self, 'from_location', None), 'rec_name', None) + route_to = route_to or getattr( + getattr(self, 'to_location', None), 'rec_name', None) + if route_from or route_to: + shipped_routes.append( + '%s > %s' % (route_from or '-', route_to or '-')) + + if not lot_qts: + for lot in line_lots: + lots_count += 1 + lot_quantity = self._execution_summary_quantity( + getattr(lot, 'lot_qt', None)) + if getattr(lot, 'lot_p', None) or getattr(lot, 'line', None): + matched += lot_quantity + shipment = ( + getattr(lot, 'lot_shipment_in', None) + or getattr(lot, 'lot_shipment_internal', None) + or getattr(lot, 'lot_shipment_out', None)) + if shipment: + shipped += lot_quantity + route_from = getattr( + getattr(shipment, 'from_location', None), + 'rec_name', None) + route_to = getattr( + getattr(shipment, 'to_location', None), + 'rec_name', None) + route_from = route_from or getattr( + getattr(self, 'from_location', None), + 'rec_name', None) + route_to = route_to or getattr( + getattr(self, 'to_location', None), + 'rec_name', None) + if route_from or route_to: + shipped_routes.append( + '%s > %s' % (route_from or '-', route_to or '-')) + + unit = getattr(getattr(line, 'unit', None), 'rec_name', '') or '' + product = getattr(getattr(line, 'product', None), 'rec_name', '') or '' + route = (shipped_routes[0] if shipped_routes else '%s > %s' % ( + getattr(getattr(self, 'from_location', None), 'rec_name', None) + or '-', + getattr(getattr(self, 'to_location', None), 'rec_name', None) + or '-')) + + rows.append({ + 'product': product, + 'quantity': quantity, + 'matched': matched, + 'shipped': shipped, + 'open': max(quantity - shipped, 0.0), + 'unit': unit, + 'lots': lots_count, + 'route': route, + }) + totals['quantity'] += quantity + totals['matched'] += matched + totals['shipped'] += shipped + totals['open'] += max(quantity - shipped, 0.0) + totals['lines'] += 1 + + data = { + 'type': 'execution-summary', + 'unit': rows[0]['unit'] if rows else '', + 'totals': dict(totals), + 'rows': rows[:4], + } + return "d3:" + json.dumps(data) + def getLots(self): if self.lines: if self.lines.lots: diff --git a/modules/purchase_trade/view/sale_form.xml b/modules/purchase_trade/view/sale_form.xml index e38174e..46749a8 100755 --- a/modules/purchase_trade/view/sale_form.xml +++ b/modules/purchase_trade/view/sale_form.xml @@ -2,57 +2,112 @@ - - -