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()