From 24b9ca1575795f0da7e4c1abf08862910f50054d Mon Sep 17 00:00:00 2001 From: Maxime Richez Date: Fri, 30 Jan 2026 17:32:18 +0100 Subject: [PATCH] Set incoterm required on extended intrastat for sale between EU countries Closes #14531 --- modules/account_stock_eu/sale.py | 6 +- modules/account_stock_eu/tests/test_module.py | 116 +++++++++++++++++- modules/incoterm/tests/test_module.py | 63 +++++++++- 3 files changed, 180 insertions(+), 5 deletions(-) diff --git a/modules/account_stock_eu/sale.py b/modules/account_stock_eu/sale.py index e7b02a0471..e0b62e80a9 100644 --- a/modules/account_stock_eu/sale.py +++ b/modules/account_stock_eu/sale.py @@ -42,7 +42,7 @@ class Sale_Incoterm(metaclass=PoolMeta): self.sale_date) else: to_europe = None - required = ( - (from_country != to_country) - and not (from_europe and to_europe)) + if (from_country != to_country + and from_europe and to_europe): + required = True return required diff --git a/modules/account_stock_eu/tests/test_module.py b/modules/account_stock_eu/tests/test_module.py index 20b9469199..399035d813 100644 --- a/modules/account_stock_eu/tests/test_module.py +++ b/modules/account_stock_eu/tests/test_module.py @@ -1,6 +1,10 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from trytond.tests.test_tryton import ModuleTestCase +from unittest.mock import Mock, patch + +from trytond.modules.account.exceptions import FiscalYearNotFoundError +from trytond.pool import Pool +from trytond.tests.test_tryton import ModuleTestCase, with_transaction class AccountStockEuTestCase(ModuleTestCase): @@ -8,5 +12,115 @@ class AccountStockEuTestCase(ModuleTestCase): module = 'account_stock_eu' extras = ['carrier', 'incoterm', 'production', 'stock_consignment'] + @with_transaction() + def test_sale_incoterm_required(self): + "Test incoterm required on sale" + pool = Pool() + Sale = pool.get('sale.sale') + FiscalYear = pool.get('account.fiscalyear') + Country = pool.get('country.country') + + from_country = Mock(spec=Country) + to_country = Mock(spec=Country) + sale = Mock(spec=Sale) + sale.warehouse.address.country = from_country + sale.shipment_address.country = to_country + type(sale)._incoterm_required = Sale._incoterm_required + fiscalyear = Mock(spec=FiscalYear) + + with patch.object(FiscalYear, 'find') as fiscalyear_find: + fiscalyear_find.return_value = fiscalyear + for extended, from_europe, to_europe, result in [ + (False, False, False, True), + (False, False, True, True), + (False, True, False, True), + (False, True, True, False), + (True, False, False, True), + (True, False, True, True), + (True, True, False, True), + (True, True, True, True), + (None, False, False, True), + (None, False, True, True), + (None, True, False, True), + (None, True, True, False), + ]: + if extended is not None: + fiscalyear_find.side_effect = None + fiscalyear.intrastat_extended = extended + else: + fiscalyear_find.side_effect = FiscalYearNotFoundError('') + from_country.is_member.return_value = from_europe + to_country.is_member.return_value = to_europe + with self.subTest( + extended=extended, + from_europe=from_europe, + to_europe=to_europe): + self.assertEqual(sale._incoterm_required, result) + + @with_transaction() + def test_sale_incoterm_required_same_country(self): + "Test incoterm required on sale with same country" + pool = Pool() + Sale = pool.get('sale.sale') + FiscalYear = pool.get('account.fiscalyear') + Country = pool.get('country.country') + + country = Mock(spec=Country) + sale = Mock(spec=Sale) + sale.warehouse.address.country = country + sale.shipment_address.country = country + type(sale)._incoterm_required = Sale._incoterm_required + fiscalyear = Mock(spec=FiscalYear) + + with patch.object(FiscalYear, 'find') as fiscalyear_find: + fiscalyear_find.return_value = fiscalyear + for extended, europe, result in [ + (False, False, False), + (False, True, False), + (True, False, False), + (True, True, False), + (None, False, False), + (None, True, False), + ]: + if extended is not None: + fiscalyear_find.side_effect = None + fiscalyear.intrastat_extended = extended + else: + fiscalyear_find.side_effect = FiscalYearNotFoundError('') + country.is_member.return_value = europe + with self.subTest( + extended=extended, + europe=europe): + self.assertEqual(sale._incoterm_required, result) + + @with_transaction() + def test_sale_incoterm_required_no_country(self): + "Test incoterm required on sale without country" + pool = Pool() + Sale = pool.get('sale.sale') + FiscalYear = pool.get('account.fiscalyear') + + sale = Mock(spec=Sale) + sale.warehouse.address.country = None + sale.shipment_address.country = None + type(sale)._incoterm_required = Sale._incoterm_required + fiscalyear = Mock(spec=FiscalYear) + + with patch.object(FiscalYear, 'find') as fiscalyear_find: + fiscalyear_find.return_value = fiscalyear + + for extended, result in [ + (False, False), + (True, False), + (None, False), + ]: + if extended is not None: + fiscalyear_find.side_effect = None + fiscalyear.intrastat_extended = extended + else: + fiscalyear_find.side_effect = FiscalYearNotFoundError('') + with self.subTest(extended=extended): + self.assertEqual(sale._incoterm_required, result) + del ModuleTestCase diff --git a/modules/incoterm/tests/test_module.py b/modules/incoterm/tests/test_module.py index c80e896ffb..383e98aafc 100644 --- a/modules/incoterm/tests/test_module.py +++ b/modules/incoterm/tests/test_module.py @@ -1,7 +1,7 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. -from unittest.mock import MagicMock +from unittest.mock import MagicMock, Mock from trytond.modules.company.tests import CompanyTestMixin from trytond.pool import Pool @@ -32,5 +32,66 @@ class IncotermTestCase(CompanyTestMixin, ModuleTestCase): self.assertLessEqual({'incoterm', 'incoterm_location'}, fields) self.assertLessEqual(fields, ShipmentOut._fields.keys()) + @with_transaction() + def test_sale_incoterm_required(self): + "Test incoterm required on sale" + pool = Pool() + Sale = pool.get('sale.sale') + Country = pool.get('country.country') + + from_country = Mock(spec=Country) + to_country = Mock(spec=Country) + sale = Mock(spec=Sale) + sale.warehouse.address.country = from_country + sale.shipment_address.country = to_country + type(sale)._incoterm_required = Sale._incoterm_required + + for from_europe, to_europe, result in [ + (False, False, True), + (False, True, True), + (True, False, True), + (True, True, False), + ]: + from_country.is_member.return_value = from_europe + to_country.is_member.return_value = to_europe + with self.subTest( + from_europe=from_europe, + to_europe=to_europe): + self.assertEqual(sale._incoterm_required, result) + + @with_transaction() + def test_sale_incoterm_required_same_country(self): + "Test incoterm required on sale with same country" + pool = Pool() + Sale = pool.get('sale.sale') + Country = pool.get('country.country') + + country = Mock(spec=Country) + sale = Mock(spec=Sale) + sale.warehouse.address.country = country + sale.shipment_address.country = country + type(sale)._incoterm_required = Sale._incoterm_required + + for europe, result in [ + (False, False), + (True, False), + ]: + country.is_member.return_value = europe + with self.subTest(europe=europe): + self.assertEqual(sale._incoterm_required, result) + + @with_transaction() + def test_sale_incoterm_required_no_country(self): + "Test incoterm required on sale without country" + pool = Pool() + Sale = pool.get('sale.sale') + + sale = Mock(spec=Sale) + sale.warehouse.address.country = None + sale.shipment_address.country = None + type(sale)._incoterm_required = Sale._incoterm_required + + self.assertFalse(sale._incoterm_required) + del ModuleTestCase