From cc6ce82ec164dee33d1c68dc7660990fb82cc3a9 Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Thu, 2 Apr 2026 16:24:18 +0200 Subject: [PATCH] 02.04.26 --- modules/purchase_trade/invoice.py | 93 +++++++++++++++++---- modules/purchase_trade/tests/test_module.py | 26 ++++-- 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py index 9025a20..fe0ed3a 100644 --- a/modules/purchase_trade/invoice.py +++ b/modules/purchase_trade/invoice.py @@ -73,6 +73,52 @@ class Invoice(metaclass=PoolMeta): return lot return line.lots[0] + def _get_report_invoice_lots(self): + invoice_lines = self._get_report_invoice_lines() + if not invoice_lines: + return [] + + def _same_invoice_line(left, right): + if not left or not right: + return False + left_id = getattr(left, 'id', None) + right_id = getattr(right, 'id', None) + if left_id is not None and right_id is not None: + return left_id == right_id + return left is right + + trade = self._get_report_trade() + trade_lines = getattr(trade, 'lines', []) if trade else [] + lots = [] + for line in trade_lines or []: + for lot in getattr(line, 'lots', []) or []: + if getattr(lot, 'lot_type', None) != 'physic': + continue + refs = [ + getattr(lot, 'sale_invoice_line', None), + getattr(lot, 'sale_invoice_line_prov', None), + getattr(lot, 'invoice_line', None), + getattr(lot, 'invoice_line_prov', None), + ] + if any( + _same_invoice_line(ref, invoice_line) + for ref in refs for invoice_line in invoice_lines): + lots.append(lot) + return lots + + @staticmethod + def _format_report_package_label(unit): + label = ( + getattr(unit, 'symbol', None) + or getattr(unit, 'rec_name', None) + or getattr(unit, 'name', None) + or 'BALE' + ) + label = label.upper() + if not label.endswith('S'): + label += 'S' + return label + def _get_report_freight_fee(self): pool = Pool() Fee = pool.get('fee.fee') @@ -329,26 +375,37 @@ class Invoice(metaclass=PoolMeta): @property def report_nb_bale(self): - net = self.report_net - line = self._get_report_trade_line() or self._get_report_invoice_line() - unit = getattr(line, 'unit', None) if line else None - if net != '' and unit: + lots = self._get_report_invoice_lots() + if lots: Uom = Pool().get('product.uom') - bale_uom = Uom.get_by_name('bale') - if not bale_uom: - bale_uoms = Uom.search([ - ('name', 'ilike', 'bale'), - ], limit=1) - bale_uom = bale_uoms[0] if bale_uoms else None - if bale_uom and getattr(unit, 'category', None) == getattr( - bale_uom, 'category', None): - bale_qty = Decimal(str( - Uom.compute_qty(unit, float(net), bale_uom, round=False) - or 0)) - bale_qty = bale_qty.quantize( + line = self._get_report_invoice_line() or self._get_report_trade_line() + invoice_unit = getattr(line, 'unit', None) if line else None + net = Decimal(str(self.report_net or 0)) + total_packages = Decimal(0) + total_weight = Decimal(0) + package_unit = None + for lot in lots: + if getattr(lot, 'lot_qt', None): + total_packages += Decimal(str(lot.lot_qt or 0)) + if not package_unit and getattr(lot, 'lot_unit', None): + package_unit = lot.lot_unit + lot_quantity = Decimal(str(getattr(lot, 'lot_quantity', 0) or 0)) + lot_unit_line = getattr(lot, 'lot_unit_line', None) + if lot_quantity and lot_unit_line and invoice_unit: + total_weight += Decimal(str( + Uom.compute_qty( + lot_unit_line, float(lot_quantity), invoice_unit, + round=False) or 0)) + if total_packages: + package_qty = total_packages + if total_weight and net: + package_qty = ( + net / (total_weight / total_packages)) + package_qty = package_qty.quantize( Decimal('1'), rounding=ROUND_HALF_UP) - if bale_qty: - return 'NB BALES: ' + str(int(bale_qty)) + if package_qty: + label = self._format_report_package_label(package_unit) + return f"NB {label}: {int(package_qty)}" sale = self._get_report_sale() if sale and sale.report_nb_bale: return sale.report_nb_bale diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index cdd655c..15110b0 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -333,25 +333,37 @@ class PurchaseTradeTestCase(ModuleTestCase): self.assertEqual(invoice.report_net, Decimal('800')) - def test_invoice_report_nb_bale_uses_uom_conversion_with_sign(self): - 'invoice final note converts signed net quantity to bale using UoM rules' + def test_invoice_report_nb_bale_uses_linked_lot_packaging(self): + 'invoice reports packaging from linked physical lots with signed prorata' Invoice = Pool().get('account.invoice') line = Mock(type='line', quantity=Decimal('-15')) line.unit = Mock(rec_name='MT') - line.unit.category = Mock() - bale_uom = Mock(category=line.unit.category) + sale_line = Mock() + lot = Mock( + lot_type='physic', + lot_qt=Decimal('700'), + lot_unit=Mock(symbol='bale'), + lot_quantity=Decimal('2000'), + lot_unit_line=line.unit, + sale_invoice_line=line, + sale_invoice_line_prov=None, + invoice_line=None, + invoice_line_prov=None, + ) + sale_line.lots = [lot] + sale = Mock(lines=[sale_line]) uom_model = Mock() - uom_model.get_by_name.return_value = bale_uom - uom_model.compute_qty.return_value = Decimal('-53.6') + uom_model.compute_qty.return_value = Decimal('2000') invoice = Invoice() + invoice.sales = [sale] invoice.lines = [line] with patch( 'trytond.modules.purchase_trade.invoice.Pool' ) as PoolMock: PoolMock.return_value.get.return_value = uom_model - self.assertEqual(invoice.report_nb_bale, 'NB BALES: -54') + self.assertEqual(invoice.report_nb_bale, 'NB BALES: -5') def test_invoice_report_positive_rate_lines_keep_positive_components(self): 'invoice final note pricing section keeps only positive component lines'