Add country organization as criteria of tax rule

Closes #14771
This commit is contained in:
Cédric Krier committed 2026-04-14 17:29:07 +02:00
1 parent ad9f7bf8f2
commit 642ff61ab4
9 files changed
+129 -7

No files matched your search

@@ -1,3 +1,4 @@
* Add organization as criteria
Version 8.0.0 - 2026-04-20
--------------------------
+65 -5
View File
@@ -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:
@@ -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
<country:model-country.country>` and `Subdivision
<country:model-country.subdivision>` to match against.
<country:model-country.country>`, `Subdivision
<country:model-country.subdivision>` and `Organization
<country:model-country.organization>` to match against.
The countries and subdivisions are taken from the origin document that applies
the tax rule:
@@ -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"
@@ -3,10 +3,19 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='origin_tax']" position="after">
<label name="from_organization"/>
<field name="from_organization"/>
<newline/>
<label name="from_country"/>
<field name="from_country"/>
<label name="from_subdivision"/>
<field name="from_subdivision"/>
<label name="to_organization"/>
<field name="to_organization"/>
<newline/>
<label name="to_country"/>
<field name="to_country"/>
<label name="to_subdivision"/>
@@ -3,10 +3,19 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/form/field[@name='origin_tax']" position="after">
<label name="from_organization"/>
<field name="from_organization"/>
<newline/>
<label name="from_country"/>
<field name="from_country"/>
<label name="from_subdivision"/>
<field name="from_subdivision"/>
<label name="to_organization"/>
<field name="to_organization"/>
<newline/>
<label name="to_country"/>
<field name="to_country"/>
<label name="to_subdivision"/>
@@ -3,8 +3,10 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='origin_tax']" position="after">
<field name="from_organization" expand="1"/>
<field name="from_country" expand="1"/>
<field name="from_subdivision" expand="1"/>
<field name="to_organization" expand="1"/>
<field name="to_country" expand="1"/>
<field name="to_subdivision" expand="1"/>
</xpath>
@@ -3,8 +3,10 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='origin_tax']" position="after">
<field name="from_organization" expand="1"/>
<field name="from_country" expand="1"/>
<field name="from_subdivision" expand="1"/>
<field name="to_organization" expand="1"/>
<field name="to_country" expand="1"/>
<field name="to_subdivision" expand="1"/>
</xpath>
@@ -3,8 +3,10 @@
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="/tree/field[@name='origin_tax']" position="after">
<field name="from_organization" expand="1"/>
<field name="from_country" expand="1"/>
<field name="from_subdivision" expand="1"/>
<field name="to_organization" expand="1"/>
<field name="to_country" expand="1"/>
<field name="to_subdivision" expand="1"/>
</xpath>