diff --git a/modules/account_invoice/purchase_commission_ict.fodt b/modules/account_invoice/purchase_commission_ict.fodt index c6e2352..e43b195 100644 --- a/modules/account_invoice/purchase_commission_ict.fodt +++ b/modules/account_invoice/purchase_commission_ict.fodt @@ -17143,7 +17143,7 @@ - <for each="line in invoice.report_commission_address.splitlines()"><line></for> + <for each="line in invoice.report_purchase_commission_address.splitlines()"><line></for> @@ -17155,11 +17155,11 @@ COMMISSION INVOICE - INVOICE N. <invoice.report_commission_invoice_number> - CONTRACT N. <invoice.report_commission_contract_number> + INVOICE N. <invoice.report_purchase_commission_invoice_number> + CONTRACT N. <invoice.report_purchase_commission_contract_number> - <format_date(invoice.report_commission_date or today, invoice.party.lang)> + <format_date(invoice.report_purchase_commission_date or today, invoice.party.lang)> @@ -17170,8 +17170,8 @@ - Bill of Lading N. <invoice.report_commission_bl_number> dated <format_date(invoice.report_commission_bl_date, invoice.party.lang) if invoice.report_commission_bl_date else ''> - M/V <invoice.report_commission_vessel> + Bill of Lading N. <invoice.report_purchase_commission_bl_number> dated <format_date(invoice.report_purchase_commission_bl_date, invoice.party.lang) if invoice.report_purchase_commission_bl_date else ''> + M/V <invoice.report_purchase_commission_vessel> @@ -17179,16 +17179,16 @@ COMMISSION - <invoice.report_commission_invoice_line> + <invoice.report_purchase_commission_invoice_line> - <invoice.report_commission_quantity_unit_upper> + <invoice.report_purchase_commission_quantity_unit_upper> - <invoice.report_commission_quantity_display> + <invoice.report_purchase_commission_quantity_display> @@ -17196,12 +17196,12 @@ Lbs - <invoice.report_commission_lbs_display> + <invoice.report_purchase_commission_lbs_display> - Your commission: <invoice.report_commission_rate_line> - <invoice.report_commission_secondary_rate_line> + Your commission: <invoice.report_purchase_commission_rate_line> + <invoice.report_purchase_commission_secondary_rate_line> VAT 0% RATE @@ -17214,10 +17214,10 @@ TOTAL DUE TO YOU - <invoice.report_commission_currency_name> + <invoice.report_purchase_commission_currency_name> - <invoice.report_commission_total_display> + <invoice.report_purchase_commission_total_display> @@ -17226,4 +17226,4 @@ </for> - \ No newline at end of file + diff --git a/modules/account_invoice/sale_commission_ict.fodt b/modules/account_invoice/sale_commission_ict.fodt index 0252f76..9e109ab 100644 --- a/modules/account_invoice/sale_commission_ict.fodt +++ b/modules/account_invoice/sale_commission_ict.fodt @@ -3639,7 +3639,7 @@ - <for each="line in invoice.report_commission_address.splitlines()"><line></for> + <for each="line in invoice.report_sale_commission_address.splitlines()"><line></for> @@ -3653,11 +3653,11 @@ COMMISSION INVOICE - INVOICE N. <invoice.report_commission_invoice_number> - CONTRACT N. <invoice.report_commission_contract_number> + INVOICE N. <invoice.report_sale_commission_invoice_number> + CONTRACT N. <invoice.report_sale_commission_contract_number> - <format_date(invoice.report_commission_date or today, invoice.party.lang)> + <format_date(invoice.report_sale_commission_date or today, invoice.party.lang)> @@ -3667,8 +3667,8 @@ - Bill of Lading N. <invoice.report_commission_bl_number> dated <format_date(invoice.report_commission_bl_date, invoice.party.lang) if invoice.report_commission_bl_date else ''> - M/V <invoice.report_commission_vessel> + Bill of Lading N. <invoice.report_sale_commission_bl_number> dated <format_date(invoice.report_sale_commission_bl_date, invoice.party.lang) if invoice.report_sale_commission_bl_date else ''> + M/V <invoice.report_sale_commission_vessel> @@ -3677,16 +3677,16 @@ COMMISSION - <invoice.report_commission_invoice_line> + <invoice.report_sale_commission_invoice_line> - <invoice.report_commission_quantity_unit_upper> + <invoice.report_sale_commission_quantity_unit_upper> - <invoice.report_commission_quantity_display> + <invoice.report_sale_commission_quantity_display> @@ -3694,12 +3694,12 @@ Lbs - <invoice.report_commission_lbs_display> + <invoice.report_sale_commission_lbs_display> - Your commission: <invoice.report_commission_rate_line> - <invoice.report_commission_secondary_rate_line> + Your commission: <invoice.report_sale_commission_rate_line> + <invoice.report_sale_commission_secondary_rate_line> VAT 0% RATE @@ -3714,10 +3714,10 @@ - <invoice.report_commission_currency_name> + <invoice.report_sale_commission_currency_name> - <invoice.report_commission_total_display> + <invoice.report_sale_commission_total_display> @@ -3727,4 +3727,4 @@ </for> - \ No newline at end of file + diff --git a/modules/purchase_trade/invoice.py b/modules/purchase_trade/invoice.py index 96f2bb5..76db961 100644 --- a/modules/purchase_trade/invoice.py +++ b/modules/purchase_trade/invoice.py @@ -319,7 +319,71 @@ class Invoice(metaclass=PoolMeta): ] return lines or list(self.lines or []) + @staticmethod + def _report_record_name(record): + if not record: + return '' + return ( + getattr(record, 'rec_name', None) + or getattr(record, 'name', None) + or getattr(record, 'code', None) + or '') + + def _get_report_commission_fee(self): + for line in self._get_report_invoice_lines(): + fee = getattr(line, 'fee', None) + if fee: + return fee + origin = getattr(line, 'origin', None) + fee = getattr(origin, 'fee_', None) + if fee: + return fee + + def _get_report_commission_lots(self): + lots = [] + seen = set() + + def add_lot(lot): + if not lot: + return + lot_id = getattr(lot, 'id', None) + key = ('id', lot_id) if lot_id is not None else ('obj', id(lot)) + if key in seen: + return + seen.add(key) + lots.append(lot) + + for line in self._get_report_invoice_lines(): + add_lot(getattr(line, 'lot', None)) + fee = self._get_report_commission_fee() + if fee and hasattr(fee, '_get_effective_fee_lots'): + for lot in fee._get_effective_fee_lots(): + add_lot(lot) + for lot in getattr(fee, 'lots', []) or []: + add_lot(lot) + return lots + + def _get_report_commission_trade_for(self, kind): + fee = self._get_report_commission_fee() + if fee: + if kind == 'sale' and getattr(fee, 'sale_line', None): + return fee.sale_line.sale + if kind == 'purchase' and getattr(fee, 'line', None): + return fee.line.purchase + for lot in self._get_report_commission_lots(): + if kind == 'sale' and getattr(lot, 'sale_line', None): + return lot.sale_line.sale + if kind == 'purchase' and getattr(lot, 'line', None): + return lot.line.purchase + records = getattr(self, kind + 's', None) + if records: + return records[0] + def _get_report_commission_trade(self): + for kind in ('sale', 'purchase'): + trade = self._get_report_commission_trade_for(kind) + if trade: + return trade for line in self._get_report_invoice_lines(): origin = getattr(line, 'origin', None) if not origin: @@ -345,6 +409,10 @@ class Invoice(metaclass=PoolMeta): trade = self._get_report_commission_trade() return getattr(trade, name, '') if trade else '' + def _get_report_commission_value_for(self, kind, name): + trade = self._get_report_commission_trade_for(kind) + return getattr(trade, name, '') if trade else '' + @property def report_commission_invoice_number(self): return self._get_report_commission_value( @@ -400,6 +468,76 @@ class Invoice(metaclass=PoolMeta): return self._get_report_commission_value( 'report_commission_secondary_rate_line') + def _get_report_commission_base_amount_display_for(self, kind): + trade = self._get_report_commission_trade_for(kind) + if trade: + base = getattr(trade, 'report_commission_base_amount_display', None) + if base: + return base + amount = ( + getattr(trade, 'untaxed_amount', None) + or getattr(trade, 'total_amount', None) + or 0) + return self._format_report_number( + amount, digits='0.01', strip_trailing_zeros=False) + return self.report_commission_total_display + + def _get_report_commission_currency_name_for(self, kind): + trade = self._get_report_commission_trade_for(kind) + currency = getattr(trade, 'currency', None) if trade else self.currency + return self._report_record_name(currency) + + def _get_report_commission_rate_line_for(self, kind): + fee = self._get_report_commission_fee() + if not fee: + return self._get_report_commission_value( + 'report_commission_rate_line') + + mode = getattr(fee, 'mode', None) + price = getattr(fee, 'price', None) or 0 + if mode in {'pprice', 'rate', 'pcost'}: + return ' '.join(part for part in [ + self._format_report_number(price, digits='0.0000'), + '% on', + kind, + 'amount', + self._get_report_commission_base_amount_display_for(kind), + self._get_report_commission_currency_name_for(kind), + ] if part) + + if (getattr(fee, 'enable_linked_currency', False) + and getattr(fee, 'linked_price', None) is not None): + price = fee.linked_price + currency = getattr(fee, 'linked_currency', None) + unit = getattr(fee, 'linked_unit', None) + else: + currency = getattr(fee, 'currency', None) + unit = getattr(fee, 'unit', None) + price_text = self._format_report_number(price, digits='0.0000') + currency_text = self._report_record_name(currency) + unit_text = self._report_record_name(unit) + if currency_text and unit_text: + return '%s %s/%s' % (price_text, currency_text, unit_text) + return ' '.join(part for part in [ + price_text, currency_text or unit_text, + ] if part) + + @property + def report_sale_commission_rate_line(self): + return self._get_report_commission_rate_line_for('sale') + + @property + def report_purchase_commission_rate_line(self): + return self._get_report_commission_rate_line_for('purchase') + + @property + def report_sale_commission_secondary_rate_line(self): + return '' + + @property + def report_purchase_commission_secondary_rate_line(self): + return '' + @property def report_commission_currency_name(self): return self._get_report_commission_value( @@ -410,6 +548,149 @@ class Invoice(metaclass=PoolMeta): return self._get_report_commission_value( 'report_commission_total_display') + def _get_report_commission_address_for(self, kind): + trade = self._get_report_commission_trade_for(kind) + return getattr(trade, 'report_address', '') or '' + + def _get_report_commission_date_for(self, kind): + trade = self._get_report_commission_trade_for(kind) + return ( + getattr(trade, 'sale_date', None) + or getattr(trade, 'purchase_date', None) + or self.invoice_date) + + def _get_report_commission_total_display_for(self, kind): + fee = self._get_report_commission_fee() + if fee and hasattr(fee, 'get_amount'): + amount = fee.get_amount() + else: + amount = self.total_amount or 0 + return self._format_report_number( + abs(Decimal(str(amount or 0))), digits='0.01', + strip_trailing_zeros=False) + + @property + def report_sale_commission_address(self): + return self._get_report_commission_address_for('sale') + + @property + def report_purchase_commission_address(self): + return self._get_report_commission_address_for('purchase') + + @property + def report_sale_commission_date(self): + return self._get_report_commission_date_for('sale') + + @property + def report_purchase_commission_date(self): + return self._get_report_commission_date_for('purchase') + + @property + def report_sale_commission_invoice_number(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_invoice_number') + + @property + def report_purchase_commission_invoice_number(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_invoice_number') + + @property + def report_sale_commission_contract_number(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_contract_number') + + @property + def report_purchase_commission_contract_number(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_contract_number') + + @property + def report_sale_commission_bl_number(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_bl_number') + + @property + def report_purchase_commission_bl_number(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_bl_number') + + @property + def report_sale_commission_bl_date(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_bl_date') + + @property + def report_purchase_commission_bl_date(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_bl_date') + + @property + def report_sale_commission_vessel(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_vessel') + + @property + def report_purchase_commission_vessel(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_vessel') + + @property + def report_sale_commission_invoice_line(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_invoice_line') + + @property + def report_purchase_commission_invoice_line(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_invoice_line') + + @property + def report_sale_commission_quantity_unit_upper(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_quantity_unit_upper') + + @property + def report_purchase_commission_quantity_unit_upper(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_quantity_unit_upper') + + @property + def report_sale_commission_quantity_display(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_quantity_display') + + @property + def report_purchase_commission_quantity_display(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_quantity_display') + + @property + def report_sale_commission_lbs_display(self): + return self._get_report_commission_value_for( + 'sale', 'report_commission_lbs_display') + + @property + def report_purchase_commission_lbs_display(self): + return self._get_report_commission_value_for( + 'purchase', 'report_commission_lbs_display') + + @property + def report_sale_commission_currency_name(self): + return self._report_record_name(self.currency) + + @property + def report_purchase_commission_currency_name(self): + return self._report_record_name(self.currency) + + @property + def report_sale_commission_total_display(self): + return self._get_report_commission_total_display_for('sale') + + @property + def report_purchase_commission_total_display(self): + return self._get_report_commission_total_display_for('purchase') + @staticmethod def _get_report_related_lots(line): lots = [] diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py index 36ad46b..b5703ff 100644 --- a/modules/purchase_trade/tests/test_module.py +++ b/modules/purchase_trade/tests/test_module.py @@ -4,6 +4,7 @@ import datetime from contextlib import nullcontext from decimal import Decimal +from types import SimpleNamespace from unittest.mock import ANY, Mock, patch from xml.etree import ElementTree @@ -5210,6 +5211,68 @@ class PurchaseTradeTestCase(ModuleTestCase): 'amount': None, }]) + def test_sale_commission_invoice_uses_linked_perqt_fee_rate(self): + 'sale commission invoice shows linked fee price per sale quantity' + Invoice = Pool().get('account.invoice') + sale = SimpleNamespace( + currency=SimpleNamespace(rec_name='USD'), + untaxed_amount=Decimal('7960'), + total_amount=Decimal('7960')) + sale_line = SimpleNamespace(sale=sale) + lot = SimpleNamespace(id=1, sale_line=sale_line, line=None) + fee = SimpleNamespace( + mode='perqt', + price=Decimal('0.02'), + enable_linked_currency=True, + linked_price=Decimal('2'), + linked_currency=SimpleNamespace(rec_name='USC'), + linked_unit=SimpleNamespace(rec_name='Kilogram'), + currency=SimpleNamespace(rec_name='USD'), + unit=SimpleNamespace(rec_name='MT'), + sale_line=sale_line, + line=None, + lots=[lot], + _get_effective_fee_lots=lambda: [lot]) + invoice = Invoice() + invoice.currency = SimpleNamespace(rec_name='USD') + invoice.lines = [ + SimpleNamespace(type='line', fee=fee, lot=lot, origin=None)] + + self.assertEqual( + invoice.report_sale_commission_rate_line, + '2 USC/Kilogram') + self.assertEqual(invoice.report_sale_commission_secondary_rate_line, '') + + def test_purchase_commission_invoice_uses_purchase_amount_for_percent_fee(self): + 'purchase commission invoice shows percent fee on purchase amount' + Invoice = Pool().get('account.invoice') + purchase = SimpleNamespace( + currency=SimpleNamespace(rec_name='USD'), + untaxed_amount=Decimal('7960'), + total_amount=Decimal('7960')) + purchase_line = SimpleNamespace(purchase=purchase) + lot = SimpleNamespace(id=1, line=purchase_line, sale_line=None) + fee = SimpleNamespace( + mode='pprice', + price=Decimal('1.5'), + enable_linked_currency=False, + currency=SimpleNamespace(rec_name='USD'), + unit=SimpleNamespace(rec_name='MT'), + sale_line=None, + line=purchase_line, + lots=[lot], + _get_effective_fee_lots=lambda: [lot]) + invoice = Invoice() + invoice.currency = SimpleNamespace(rec_name='USD') + invoice.lines = [ + SimpleNamespace(type='line', fee=fee, lot=lot, origin=None)] + + self.assertEqual( + invoice.report_purchase_commission_rate_line, + '1.5 % on purchase amount 7960.00 USD') + self.assertEqual( + invoice.report_purchase_commission_secondary_rate_line, '') + def test_fee_delete_detects_generated_ordered_purchase(self): 'ordered fee deletion targets only its generated service purchase' Fee = Pool().get('fee.fee')