diff --git a/modules/account_invoice/invoice_ict_final.fodt b/modules/account_invoice/invoice_ict_final.fodt
index 3ed36be..029d29c 100644
--- a/modules/account_invoice/invoice_ict_final.fodt
+++ b/modules/account_invoice/invoice_ict_final.fodt
@@ -3999,7 +3999,9 @@
- At <invoice.report_rate_currency_upper><invoice.report_rate_value>PER <invoice.report_rate_unit_upper>(<invoice.report_rate_price_words>) <invoice.report_rate_pricing_text>
+ <for each="line in invoice.report_positive_rate_lines.splitlines()">
+ At <line>
+ </for>
FREIGHT VALUE: <invoice.report_freight_currency_symbol><format_number(invoice.report_freight_amount, invoice.party.lang) if invoice.report_freight_amount != '' else ''>
diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py
index 7195db4..a706c8d 100644
--- a/modules/purchase_trade/invoice.py
+++ b/modules/purchase_trade/invoice.py
@@ -31,6 +31,14 @@ class Invoice(metaclass=PoolMeta):
]
return lines or list(self.lines or [])
+ @staticmethod
+ def _clean_report_description(value):
+ text = (value or '').strip()
+ normalized = text.replace(' ', '').upper()
+ if normalized == 'PROFORMA':
+ return ''
+ return text.upper() if text else ''
+
def _get_report_purchase(self):
purchases = list(self.purchases or [])
return purchases[0] if purchases else None
@@ -141,7 +149,7 @@ class Invoice(metaclass=PoolMeta):
@property
def report_description_upper(self):
if self.lines:
- return (self.lines[0].description or '').upper()
+ return self._clean_report_description(self.lines[0].description)
return ''
@property
@@ -259,6 +267,40 @@ class Invoice(metaclass=PoolMeta):
details.append(detail)
return '\n'.join(details)
+ @property
+ def report_positive_rate_lines(self):
+ sale = self._get_report_sale()
+ if sale and getattr(sale, 'report_price_lines', None):
+ return sale.report_price_lines
+ details = []
+ for line in self._get_report_invoice_lines():
+ quantity = getattr(line, 'report_net', '')
+ if quantity == '':
+ quantity = getattr(line, 'quantity', '')
+ if Decimal(str(quantity or 0)) <= 0:
+ continue
+ currency = getattr(line, 'report_rate_currency_upper', '') or ''
+ value = getattr(line, 'report_rate_value', '')
+ value_text = ''
+ if value != '':
+ value_text = self._format_report_number(
+ value, strip_trailing_zeros=False)
+ unit = getattr(line, 'report_rate_unit_upper', '') or ''
+ words = getattr(line, 'report_rate_price_words', '') or ''
+ pricing_text = getattr(line, 'report_rate_pricing_text', '') or ''
+ detail = ' '.join(
+ part for part in [
+ currency,
+ value_text,
+ 'PER' if unit else '',
+ unit,
+ f"({words})" if words else '',
+ pricing_text,
+ ] if part)
+ if detail:
+ details.append(detail)
+ return '\n'.join(details)
+
@property
def report_payment_date(self):
trade = self._get_report_trade()
@@ -287,6 +329,12 @@ class Invoice(metaclass=PoolMeta):
@property
def report_nb_bale(self):
+ unit = self.report_weight_unit_upper
+ net = self.report_net
+ if net != '' and unit == 'MT':
+ quantity = abs(Decimal(str(net or 0))).quantize(Decimal('1'))
+ if quantity:
+ return 'NB BALES: ' + str(int(quantity))
sale = self._get_report_sale()
if sale and sale.report_nb_bale:
return sale.report_nb_bale
@@ -473,7 +521,7 @@ class InvoiceLine(metaclass=PoolMeta):
@property
def report_description_upper(self):
- return (self.description or '').upper()
+ return Invoice._clean_report_description(self.description)
@property
def report_rate_currency_upper(self):
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index 0031903..0bec76f 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -333,6 +333,37 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(invoice.report_net, Decimal('800'))
+ def test_invoice_report_nb_bale_uses_abs_mt_difference(self):
+ 'invoice final note displays bale count as rounded MT differential'
+ Invoice = Pool().get('account.invoice')
+
+ line = Mock(type='line', quantity=Decimal('-15'))
+ line.unit = Mock(rec_name='MT')
+ invoice = Invoice()
+ invoice.lines = [line]
+
+ self.assertEqual(invoice.report_nb_bale, 'NB BALES: 15')
+
+ def test_invoice_report_positive_rate_lines_keep_positive_components(self):
+ 'invoice final note pricing section keeps only positive component lines'
+ Invoice = Pool().get('account.invoice')
+ sale = Mock()
+ sale.report_price_lines = (
+ 'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026\n'
+ 'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MAY 2026'
+ )
+
+ invoice = Invoice()
+ invoice.sales = [sale]
+ invoice.lines = []
+
+ self.assertEqual(
+ invoice.report_positive_rate_lines.splitlines(),
+ [
+ 'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026',
+ 'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MAY 2026',
+ ])
+
def test_lot_invoice_sale_uses_sale_invoice_line_reference(self):
'sale invoicing must resolve the generated invoice from sale invoice links'
sale_invoice = Mock()