diff --git a/modules/account_tax_rule_country/CHANGELOG b/modules/account_tax_rule_country/CHANGELOG index b09a055f46..46f2385634 100644 --- a/modules/account_tax_rule_country/CHANGELOG +++ b/modules/account_tax_rule_country/CHANGELOG @@ -1,3 +1,4 @@ +* Add organization as criteria Version 8.0.0 - 2026-04-20 -------------------------- diff --git a/modules/account_tax_rule_country/account.py b/modules/account_tax_rule_country/account.py index 52cd792ec9..27aa90aa59 100644 --- a/modules/account_tax_rule_country/account.py +++ b/modules/account_tax_rule_country/account.py @@ -3,7 +3,8 @@ from trytond.model import fields from trytond.pool import Pool, PoolMeta -from trytond.pyson import Eval +from trytond.pyson import Bool, Eval, If +from trytond.transaction import Transaction class TaxRule(metaclass=PoolMeta): @@ -32,9 +33,29 @@ class TaxRule(metaclass=PoolMeta): class _TaxRuleLineMixin: __slots__ = () + + from_organization = fields.Many2One( + 'country.organization', "From Organization", ondelete='RESTRICT', + domain=[ + If(Eval('from_country'), + ('id', '=', -1), + ()), + ], + states={ + 'invisible': Bool(Eval('from_country')), + }, + help="Apply only to the addresses from this organization.") from_country = fields.Many2One( 'country.country', 'From Country', ondelete='RESTRICT', - help="Apply only to addresses of this country.") + domain=[ + If(Eval('from_organization'), + ('id', '=', -1), + ()), + ], + states={ + 'invisible': Bool(Eval('from_organization')), + }, + help="Apply only to the addresses from this country.") from_subdivision = fields.Many2One( 'country.subdivision', "From Subdivision", ondelete='RESTRICT', domain=[ @@ -43,10 +64,29 @@ class _TaxRuleLineMixin: states={ 'invisible': ~Eval('from_country'), }, - help="Apply only to addresses in this subdivision.") + help="Apply only to the addresses from this subdivision.") + to_organization = fields.Many2One( + 'country.organization', "To Organization", ondelete='RESTRICT', + domain=[ + If(Eval('to_country'), + ('id', '=', -1), + ()), + ], + states={ + 'invisible': Bool(Eval('to_country')), + }, + help="Apply only to the addresses of this organization.") to_country = fields.Many2One( 'country.country', 'To Country', ondelete='RESTRICT', - help="Apply only to addresses of this country.") + domain=[ + If(Eval('to_organization'), + ('id', '=', -1), + ()), + ], + states={ + 'invisible': Bool(Eval('to_organization')), + }, + help="Apply only to the addresses to this country.") to_subdivision = fields.Many2One( 'country.subdivision', "To Subdivision", ondelete='RESTRICT', domain=[ @@ -55,7 +95,7 @@ class _TaxRuleLineMixin: states={ 'invisible': ~Eval('to_country'), }, - help="Apply only to addresses in this subdivision.") + help="Apply only to the addresses to this subdivision.") class TaxRuleLineTemplate(_TaxRuleLineMixin, metaclass=PoolMeta): @@ -85,6 +125,26 @@ class TaxRuleLine(_TaxRuleLineMixin, metaclass=PoolMeta): __name__ = 'account.tax.rule.line' def match(self, pattern): + pool = Pool() + Date = pool.get('ir.date') + Country = pool.get('country.country') + + with Transaction().set_context(company=self.rule.company.id): + date = pattern.get('date') or Date.today() + + for organization, name in [ + (self.from_organization, 'from_country'), + (self.to_organization, 'to_country'), + ]: + if organization: + pattern = pattern.copy() + country = pattern.pop(name, None) + if not country: + return False + country = Country(country) + if not country.is_member(organization, date=date): + return False + for name in ['from_subdivision', 'to_subdivision']: subdivision = getattr(self, name) if name not in pattern: diff --git a/modules/account_tax_rule_country/doc/design.rst b/modules/account_tax_rule_country/doc/design.rst index b680830305..00032e89ed 100644 --- a/modules/account_tax_rule_country/doc/design.rst +++ b/modules/account_tax_rule_country/doc/design.rst @@ -11,8 +11,9 @@ Tax Rule When the *Account Tax Rule Country Module* is activated, the *Tax Rule* gains additional criteria for the origin and destination `Country -` and `Subdivision -` to match against. +`, `Subdivision +` and `Organization +` to match against. The countries and subdivisions are taken from the origin document that applies the tax rule: diff --git a/modules/account_tax_rule_country/tests/test_module.py b/modules/account_tax_rule_country/tests/test_module.py index c22ea50efe..f34782ac4a 100644 --- a/modules/account_tax_rule_country/tests/test_module.py +++ b/modules/account_tax_rule_country/tests/test_module.py @@ -35,10 +35,14 @@ class AccountTaxRuleCountryTestCase(CompanyTestMixin, ModuleTestCase): @classmethod def _create_countries(cls): pool = Pool() + Organization = pool.get('country.organization') Country = pool.get('country.country') Subdivision = pool.get('country.subdivision') + organization1 = Organization(name="Org 1") + organization1.save() country1 = Country(name="Country 1") + country1.members = [{'organization': organization1}] country1.save() subdivision1 = Subdivision( country=country1, name="Subdivision 1", code="SUB1", @@ -48,7 +52,11 @@ class AccountTaxRuleCountryTestCase(CompanyTestMixin, ModuleTestCase): country=country1, parent=subdivision1, name="Sub-Subdivision 1", code="SUBSUB1", type='province') subdivision11.save() + + organization2 = Organization(name="Org 2") + organization2.save() country2 = Country(name="Country 2") + country2.members = [{'organization': organization2}] country2.save() subdivision2 = Subdivision( country=country2, name="Subdivision 2", code="SUB2", @@ -126,6 +134,34 @@ class AccountTaxRuleCountryTestCase(CompanyTestMixin, ModuleTestCase): self.assertListEqual(tax_rule.apply(tax, pattern), [target_tax.id]) + @with_transaction() + def test_tax_rule_organization(self): + "Test tax rule with organization" + country1, country2 = self._create_countries() + organization1 = country1.organizations[0] + organization2 = country2.organizations[0] + company = create_company() + with set_company(company): + create_chart(company, tax=True) + tax, target_tax = self._get_taxes()[:2] + tax_rule = self._create_rule([{ + 'from_organization': organization1.id, + 'to_organization': organization2.id, + 'origin_tax': tax.id, + 'tax': target_tax.id, + }]) + + for pattern, result in [ + ({'from_country': country1.id, 'to_country': country2.id}, + [target_tax.id]), + ({'from_country': country2.id, 'to_country': country1.id}, + [tax.id]), + ({'from_country': None, 'to_country': country2.id}, + [tax.id]), + ]: + with self.subTest(pattern=pattern): + self.assertListEqual(tax_rule.apply(tax, pattern), result) + @with_transaction() def test_tax_rule_no_subdivision(self): "Test tax rule without subdivision" diff --git a/modules/account_tax_rule_country/view/tax_rule_line_form.xml b/modules/account_tax_rule_country/view/tax_rule_line_form.xml index 9f6007a727..91c28e2463 100644 --- a/modules/account_tax_rule_country/view/tax_rule_line_form.xml +++ b/modules/account_tax_rule_country/view/tax_rule_line_form.xml @@ -3,10 +3,19 @@ this repository contains the full copyright notices and license terms. --> +